package org.fjsei.yewu.maintenance;

import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;       //注意同名Query
import com.alibaba.fastjson2.JSON;
import com.gauck.sam.Utilities.Utilities;
import com.gauck.sam.Utilities.model.*;
import com.gauck.sam.Utilities.model.jc.*;
import com.gauck.sam.Utilities.model.jy.Allunit;
import com.gauck.sam.Utilities.model.jy.Housebd;
import com.gauck.sam.Utilities.model.jy.JianyanBumen;
import com.gauck.sam.Utilities.model.jy.OdlqArea;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Predicate;
import lombok.extern.slf4j.Slf4j;
import md.cm.base.*;
import md.cm.geography.*;
import md.cm.unit.Division;
import md.cm.unit.DivisionRepository;
import md.cm.unit.Unit;
import md.specialEqp.*;
import md.specialEqp.inspect.*;
import md.specialEqp.type.*;
import md.system.User;
//import org.elasticsearch.index.query.ExistsQueryBuilder;
//import org.elasticsearch.index.query.TermsQueryBuilder;
import org.fjsei.yewu.aop.MetricsLogger;
import org.fjsei.yewu.aop.hibernate.MyMassIndexingLoggingMonitor;
import org.fjsei.yewu.entity.fjtj.*;
import org.fjsei.yewu.entity.incp.*;
import org.fjsei.yewu.exception.UserAccessDenyException;
import org.fjsei.yewu.index.*;
import org.fjsei.yewu.jpa.PageOffsetFirst;
import org.fjsei.yewu.jpa.QBeanMy;
import org.fjsei.yewu.pojo.SliceSyncRes;
import org.fjsei.yewu.pojo.jsn.*;
import org.fjsei.yewu.pojo.sei.PipingUnitBuildContext;
import org.fjsei.yewu.repository.StoreSync;
import org.fjsei.yewu.repository.StoreSyncRepository;
import org.fjsei.yewu.repository.maint.JsliceMang;
import org.fjsei.yewu.repository.maint.JsliceMangRepository;
import org.fjsei.yewu.resolver.MaintComn;
import org.fjsei.yewu.security.JwtUserDetailsService;
import org.fjsei.yewu.service.SearchService;
import org.fjsei.yewu.util.Tool;
import org.hibernate.search.mapper.pojo.massindexing.impl.PojoMassIndexingLoggingMonitor;
import org.jgroups.util.Triple;
import org.jgroups.util.Tuple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.domain.*;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.FluentQuery;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.execution.BatchLoaderRegistry;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

import java.time.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import static java.lang.Math.abs;
//import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.fjsei.yewu.util.Tool.longstr2Date;
import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW;


//后台维护!

@Slf4j
@Controller
public class MaintenanceMutation extends MaintComn  {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private GenericApplicationContext context;
    @Autowired
    private JwtUserDetailsService jwtUserDetailsService;
    @Autowired
    private Equipments eQPRepository;
   // @Autowired    private EqpEsRepository eqpEsRepository;
    @Autowired   private IspRepository iSPRepository;
    //@Autowired    private Units units;
    @Autowired   private CompanyEsRepository companyEsRepository;
    @Autowired   private Persons persons;
    @Autowired   private PersonEsRepository personEsRepository;
    @Autowired   private JcUntMgeRepository jcUntMgeRepository;

    //@PersistenceContext(unitName = "entityManagerFactorySei")
    //private EntityManager emSei;                //EntityManager相当于hibernate.Session：
    @PersistenceContext(unitName = "entityManagerFactoryFjtj")
    private EntityManager emFjtj;
    @PersistenceContext(unitName = "entityManagerFactoryIncp")
    private EntityManager emIncp;

//    @Autowired
//    private final JwtTokenUtil jwtTokenUtil=new JwtTokenUtil();
    @Autowired private UntMgeRepository untMgeRepository;
    @Autowired private DictAreaRepository dictAreaRepository;
    @Autowired private ProvinceRepository provinceRepository;
    @Autowired private CountryRepository countryRepository;
    @Autowired private CityRepository cityRepository;
    @Autowired private CountyRepository countyRepository;
    @Autowired private TownRepository  townRepository;
    @Autowired private AdminunitRepository adminunitRepository;
    @Autowired private HouseMgeRepository houseMgeRepository;
    @Autowired private VillageRepository villageRepository;
    @Autowired private UntDeptRepository untDeptRepository;
    @Autowired private UntSecudeptRepository untSecudeptRepository;
    @Autowired private DivisionRepository divisionRepository;
    @Autowired private StoreSyncRepository storeSyncRepository;
    @Autowired private JsliceMangRepository jsliceMangRepository;
    @Autowired private JcdunitRepository jcdunitRepository;
    @Autowired private JispUnitRepository jispUnitRepository;
    @Autowired private JuntSecudeptRepository juntSecudeptRepository;
    @Autowired private JcuntDeptRepository jcuntDeptRepository;
    @Autowired private JcPermtUntRepository jcPermtUntRepository;
    @Autowired private PipingUnitRepository  pipingUnitRepository;
    @Autowired private HrDeptinfoRepository  hrDeptinfoRepository;
    @Autowired  private TaskRepository taskRepository;

    private final DetailRepository  details;
    private final Companies companies;

    private volatile ReadComm buffAKread=null;  //缓存刚刚独到的大数据集:AK作业，分片作业可以重复利用，offs复位时手动释放。 不用static；synchronized
    @Autowired  private SearchService searchService;

    public MaintenanceMutation(BatchLoaderRegistry registry, Companies companies, DetailRepository details) {
        this.companies = companies;
        this.details = details;
    }
    /**监察单位自己，因为字段@Id jcuntId;所以重复运行并没有重复记录。
     * */
    public Boolean syncFromLegacy_Eu监察自己jC(String arg) {
        if(!emIncp.isJoinedToTransaction())      emIncp.joinTransaction();
        ReadComm<Jcunit> result= Utilities.getItArrType(Jcunit.class,213,"1=1");
        int countall=result.getArr().size();
        //终端交互才会用System.out.println((new String()).format("例行Utilities.average运行=%d",result.getErrorCode()) );
        log.info("监察自己: 条数{},服务应答{}", countall,result.getErrorCode());
        int nowcnt=0;
        int lopct=0;
        List<Jcdunit> batch=new ArrayList<>();
        for (Object one1: result.getArr())
        {
            Jcunit one= (Jcunit)one1;
            String json = JSON.toJSONString(one);       //支持自循环嵌套不报错
            Jcdunit obj=JSON.parseObject(json, Jcdunit.class);
            //找到Unit或生成新的：合并到统一单位表，不要拆开成多个单独表！
            QCompany qm = QCompany.company;
            BooleanBuilder builder = new BooleanBuilder();
            builder.and(qm.name.eq(one.getUntName()));
            //builder.and(qm.no.eq(one.getUntOrgCod()));
            Unit unit;      //允许增量 更新：单位已经存在了!
            Company company= companies.findOne(builder).orElse(null);
            if(null!=company){
                unit= units.findUnitByCompany(company);
                if(null==unit)  return false;
            }else{
                company=Company.builder().name(one.getUntName()).no(one.getUntOrgCod())
                        .build();
                unit=Unit.builder().build();
            }
            Adminunit adminunit=adminunitRepository.findTopByAreacode(one.getUntAreaCod());
            company= company.toBuilder().phone(one.getUntPhone()).address(one.getUntAddr()).linkMen(one.getUntLkmen())
                    .ad(adminunit).build();
            unit= unit.toBuilder().company(company).area(one.getUntAreaCod())
                    .build();
            try {
                companies.save(company);
                units.save(unit);
            } catch (Exception e) {
                e.printStackTrace();
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return false;
            }
            CompanyEs companyEs=new CompanyEs();
            BeanUtils.copyProperties(company,companyEs);
            companyEsRepository.save(companyEs);    //运行较慢的原因在这！
            //准备Unit结束
            obj.setUunit(unit.getId());    //两个表一起生成数据。
            lopct++;
            nowcnt++;
            batch.add(obj);
            log.info("监察自己:{}-> {}",nowcnt, obj.getUntName());
            if(lopct>=200) {
                jcdunitRepository.saveAll(batch);
                lopct=0;
                batch.clear();
            }
        }
        jcdunitRepository.saveAll(batch);
        log.info("监察自己:完成， 条数{}", countall);
        return true;
    }
    public Boolean setupFromLegacy_JU(String arg) {
        if("jS".equals(arg))    return setupFromLegacy_JU检验分支(arg);
        else if("Bm".equals(arg))    return setupFromLegacy_JU检验部门(arg);
        else if("jF".equals(arg))    return setupFromLegacy_JU监察分支(arg);
        else if("DP".equals(arg))    return setupFromLegacy_DP检验HR部门(arg);
        else if("Pz".equals(arg))    return setupFromLegacy_Pz部门生成(arg);
        else if("Ls".equals(arg))    return setupFromLegacy_Ls临时Isp消除(arg);
        else return false;
    }
    /**检验旧平台的所有单位 ：【前置】人工删除旧的表数据！
     *untMgeRepository不是主数据库的！例外要求必须controller入口加上@Transactional并且emFjtj.joinTransaction();
     * */
    public Boolean syncFromLegacy_AU检验整Jy(String type, String arg) {
        String cond="1=1";
        String steps=arg.substring(3);
        Integer stp=Integer.valueOf(steps);
        Assert.isTrue(stp>0 && stp<=4,"没预计");
        if(1==stp)  cond="UNT_ID<=69000";
        else if(2==stp)  cond="UNT_ID>69000 AND UNT_ID<=170000";
        else if(3==stp)  cond="UNT_ID>170000 AND UNT_ID<=278290";
        else if(4==stp)  cond="UNT_ID>278290";
        log.info("syncFromLegacy_AU检验整Jy:arg={},cond:{}",arg, cond);
        return syncFromLegacy_AU检验整Jy_Inner(arg,cond);
    }
    public Boolean syncFromLegacy_AU检验整Jy_Inner(String arg, String cond) {
        if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
        Assert.isTrue(emFjtj.isJoinedToTransaction(),"没emFjtj Transaction");
        Allunit allunit=new Allunit();
        String mask =Utilities.reflectDbItems(allunit);
        //String cond="1=1";     //UNT_ID IN(40781,88389,19221,40781,19221)
        log.info("syncEqpFromOld:AU arg={},过滤{}",arg, cond);
        ReadComm<Allunit> result= Utilities.catchArrType(Allunit.class,13,cond,mask);
        int countall=result.getArr().size();    //太多194194条，recv还可以，就是json2Object运行太慢! #可能堆内存溢出
        //终端交互才会用System.out.println((new String()).format("例行Utilities.average运行=%d",result.getErrorCode()) );
        log.info("syncEqpFromOld:type={},条数{},服务应答{}","AU Arg="+arg, countall,result.getErrorCode());
        return syncFromLegacy_AU检验整Jy_InDB(arg, result);
    }
    /**事务经常会失败还会重做的*/
    @Transactional(propagation = REQUIRES_NEW)
    public Boolean syncFromLegacy_AU检验整Jy_InDB(String arg, ReadComm<Allunit> result) {
        if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
        Assert.isTrue(emFjtj.isJoinedToTransaction(),"没emFjtj Transaction");
        int countall=result.getArr().size();
        log.info("syncFromLegacy_AU检验整Jy_InDB:type={},条数{},服务应答{}","AU Arg="+arg, countall,result.getErrorCode());
        int nowcnt=0;
        int lopct=0;
        List<UntMge> batch=new ArrayList<>();
        for (Object one1: result.getArr())
        {
            Allunit one= (Allunit)one1;
            String json = JSON.toJSONString(one);       //支持自循环嵌套不报错
            UntMge obj=JSON.parseObject(json, UntMge.class);
            lopct++;
            nowcnt++;
            batch.add(obj);
            if(lopct>=800) {        //每一批800条，但没提交，无法查询到的，虽然实际已写入数据库磁盘文件中。
                untMgeRepository.saveAll(batch);
                //untMgeRepository.flush();
                lopct=0;
                batch.clear();
                log.info("syncEqpFromOld:do数{}",nowcnt);
            }
        }
        untMgeRepository.saveAll(batch);
        //untMgeRepository.flush(); //报错no transaction is in progress
        log.info("syncEqpFromOld:完成，AU arg={},条数{}",arg, countall);
        return true;
    }
    /**旧的单位表: 非分片任务。
     * */
    public Boolean syncEqpFromLegacy_AU(String type, String arg) {
        if(arg.startsWith("Jy."))    return syncFromLegacy_AU检验整Jy(type,arg);
        else if("Pm".equals(arg))    return syncFromLegacy_AU永久Pm(arg);
        else if("cM".equals(arg))    return syncFromLegacy_AU监单位普cM(arg);
        return true;
    }
    public Boolean syncEqpFromLegacy_Eu(String type, String arg) {
        if("jC".equals(arg))    return syncFromLegacy_Eu监察自己jC(arg);
        else if("IS".equals(arg))    return syncFromLegacy_Eu检验机构IS(arg);
        return true;
    }
    /**极个别字段oid cod svu ispu不能修改;复制基本台账信息, 各设备种类都用的参数；扣除最基础几个字段(分支条件)。
     * 返回Tuple是两个数据合体的{resultStr 错误信息, eqp 本体ref由于build变动后}。
     * 旧检验平台没有CERT_*4个字段=必须用监察的数据CERT_DATE 发证日期,CERT_EFF_DATE 证书有效期,CERT_STATUS 证书状态,FIRSTCERT_DATE 首次发证日期。
     * */
    public Tuple fillEqpBase(Eqp eqp, Boolean usjc, Bigetly jc, Bigetly jy) {
        String retRes = "";
        Bigetly bsd=usjc? jc: jy;   //确保!null
        Assert.isTrue(eqp != null,"未找到eQP");
        Eqp.EqpBuilder<?, ?>  eqpBld=eqp.toBuilder();
        DeviceComnSvp svp=new DeviceComnSvp();    //各设备种类都有的svp,pa参数
        DeviceComnPam pam=new DeviceComnPam();
        Unit 设计单位=getUnitfromOldUnitId(usjc, bsd.getDesignUntId());
        Unit 型试单位=getUnitfromOldUnitId(usjc, bsd.getTestUntId());
        Unit 造监检单=getUnitfromOldUnitId(usjc, bsd.getMakeIspUntId());
        //svp pa 两个特殊：可修改地方多。
        svp= svp.toBuilder().年限(bsd.getDesignUseYear()).重监控("1".equals(bsd.getIfMajctl()) || "是".equals(bsd.getIfMajctl()) )
                .制造国(bsd.getMakeCountry()).大修周期(bsd.getAltCycle()).维周期(bsd.getMantCycle())
                .设计日期(null!=bsd.getDesignDate()? bsd.getDesignDate().toLocalDate() : null)
                .造监检单(null!=造监检单? new AjsonUnit(造监检单):null)
                .竣验日(null!=jy? null!=jy.getCompeAccpDate()? jy.getCompeAccpDate().toLocalDate() : null : null).产品标准(bsd.getProductMeasure())
                .设计图号(bsd.getDesignPic()).总重量(bsd.getEqpWeight())
                .装项负责(bsd.getInstLeader()).装联电(bsd.getInstLkphone()).型试报告(bsd.getTestRepcod())
                .设计单位(null!=设计单位? new AjsonUnit(设计单位):null).设鉴单位(bsd.getDesignChkunt()).型试单位(null!=型试单位? new AjsonUnit(型试单位):null)
                .build();
        pam= pam.toBuilder().急救电(bsd.getEmergencyTel()).维保电(bsd.getMantPhone()).急救人(null!=jy? jy.getEmergencyUserName():null)
                .联系电(bsd.getUsePhone()).联系人(bsd.getUseLkmen())
                .build();

        if(StringUtils.hasText(bsd.getEqpVart()))   eqpBld=eqpBld.vart(bsd.getEqpVart().substring(0,3));
        int icpa=0;
        Byte cpa=null;
        if(null!=bsd.getSafeLev())      icpa=bsd.getSafeLev();
        else if(null!=bsd.getAcciType() && 0<bsd.getAcciType())   icpa=6 - bsd.getAcciType();
        if(icpa>0 && icpa<=5)   cpa=(byte)icpa;
        //impt 旧检验平台变动了 0='国产', 1='部件进口' ,2='整机进口' 转换下编码：1='国产', 2='部件进口' ,3='整机进口'
        int impt=1;
        if(null != bsd.getImportType()) {    //监察平台编码 1='国产', 2='部件进口' ,3='整机进口'
            if("3".equals(bsd.getImportType()) || "整机进口".equals(bsd.getImportType()))   impt=3;
            else if("2".equals(bsd.getImportType()) || "部件进口".equals(bsd.getImportType()))   impt=2;
        }
        Float  money= null!=jy? jy.getEqpPrice(): null;
        money= (null!=money && money>0f)? money: null!=jc? jc.getEqpSellPrice(): null;
        Boolean  cping= null!=jy? jy.getIfIncping(): null;
        eqpBld= eqpBld.reg(RegState_Enum.values()[bsd.getEqpRegSta()]).ust(UseState_Enum.values()[bsd.getEqpUseSta()])
                .uscd(null!=bsd.getUsestaChgDate()? bsd.getUsestaChgDate().toLocalDate() : null)
                .cerd((null!=jc && null!=jc.getCertDate())? jc.getCertDate().toLocalDate() : null)
                .plno(bsd.getEqpInnerCod()).money(money).cping(cping)
                .fno(bsd.getFactoryCod()).sno(bsd.getEqpStationCod()).rnam(bsd.getRegUserName()).move(bsd.getIsMoveeqp())
                .vital("1".equals(bsd.getIfMajeqp()) || "是".equals(bsd.getIfMajeqp()) ).cpa(cpa).impt((byte)impt)
                .mkd(null!=bsd.getMakeDate()? bsd.getMakeDate().toLocalDate() : null)
                .nxtd1(null!=bsd.getNextIspDate1()? bsd.getNextIspDate1().toLocalDate() : null)
                .nxtd2(null!=bsd.getNextIspDate2()? bsd.getNextIspDate2().toLocalDate() : null)
                .lpho(null!=jy? jy.getUseMobile() : null).plat(bsd.getCatlicennum())
                .used(null!=bsd.getFirstuseDate()? bsd.getFirstuseDate().toLocalDate() : null)
                .insd(null!=jc? null!=jc.getInstCompDate()? jc.getInstCompDate().toLocalDate() : null : null)
                .expire(null!=bsd.getDesignUseOveryear()? bsd.getDesignUseOveryear().toLocalDate() : null);
        if(null!=bsd.getRegUntId()) {
            //11-18：质量技术监督局? todo: 映射？ 非注册的
            Jcdunit jcdunit = jcdunitRepository.findByJcuntId(bsd.getRegUntId());
            if (null != jcdunit && null != jcdunit.getUunit())
                eqpBld = eqpBld.regu(units.findById(jcdunit.getUunit()).orElse(null));
        }
        if(null!=bsd.getUseUntId())
                eqpBld = eqpBld.useu(getUnitfromOldUnitId(usjc,bsd.getUseUntId(),false));
        if(null!=bsd.getPropUntId())
                eqpBld = eqpBld.owner(getUnitfromOldUnitId(usjc,bsd.getPropUntId(),false));
        if(null!=bsd.getMantUntId())
                eqpBld = eqpBld.mtu(getUnitfromOldUnitId(usjc,bsd.getMantUntId(),false));
        Adminunit adminunit=adminunitRepository.findTopByAreacode(bsd.getEqpAreaCod());
        Village village=null;
        if(null!=jy)    village=villageRepository.findTopByOldId(jy.getBuildId());
        else if(null!=jc && null!=adminunit && StringUtils.hasText(jc.getBuildname()) ) {
            village=villageRepository.findByAdAndName(adminunit, jc.getBuildname());
            if(null==village) {      //厦门:自动增加一个Village： 但设置oldId=0；address=null; 特别标记!
                village=new Village();
                village.setAd(adminunit);
                village.setName(jc.getBuildname());
                village.setOldId(0L);
                village.setAddress(null);
                villageRepository.save(village);
            }
        }
        Double 纬度=null;   //latitude  -90<>90    必须约束,不合法数据存入ES报错！
        Double 经度=null;   //longitude  -180<>180
        if(null!=bsd.getEqpLat() && null!=bsd.getEqpLong()  && bsd.getEqpLat()>= -90 && bsd.getEqpLat()<= 90
                    && bsd.getEqpLong()>= -180 && bsd.getEqpLong()<= 180 ){
            纬度=bsd.getEqpLat();
            经度=bsd.getEqpLong();
        }
        //监察平台：单位id竟然关联不同的很多表。
        eqpBld = eqpBld.makeu(getUnitfromOldUnitId(usjc, bsd.getMakeUntId())).insu(getUnitfromOldUnitId(usjc, bsd.getInstUntId()))
                .remu(getUnitfromOldUnitId(usjc, bsd.getAltUntId())).repu(getUnitfromOldUnitId(usjc, bsd.getOvhUntId()))
                .plat(bsd.getCatlicennum()).address(bsd.getEqpUseAddr()).lon(经度).lat(纬度)
                .plcls(mapFromEqpUsePlace(bsd.getEqpUsePlace())).dense(bsd.getIfPopulated()).seimp(bsd.getIfMajplace())
                .ad(adminunit).vlg(village).unqf1(null!=jy? jy.getNoteligibleFalg1() : null)
                .unqf2(null!=jy? jy.getNoteligibleFalg2() : null);
        //不止一个啊:plat?=>罐车牌号 VesselPrm."CARSIGN" ?or 厂车牌照 CarDrive.CATLICENNUM ||bsd.catlicennum 原来两处存储?
        //【删除字段】土建施单，固资值;土建验单,施工类别,施工日期;施工号,设计许号;合格证号; 都是仅存于监察临时表的？是施工告知单子,本平台不搞了。
        //【最近一次定期检验1,2】构建isp1 2? =外部报告new|Find?(简易4字段)；外部报告只有只简单的IspBase{却不是IspCold(没人关联的历史报告)},管道装置不需要同步。
        BusinessCat_Enum bsType;
        Isp isp1=null, isp2=null;
        if(!"8".equals(eqp.getType())) {        //管道=8,放在单元同步阶段做isp
            Isp.IspBuilder ispBuilder;
            if (StringUtils.hasText(bsd.getLastIspReport1())) {
                isp1 = iSPRepository.findTopByDevAndNo(eqp, bsd.getLastIspReport1());
                if (null == isp1) {
                    ispBuilder = new Isp().toBuilder().dev(eqp).no(bsd.getLastIspReport1());
                } else ispBuilder = isp1.toBuilder();
                Long optype = bsd.getLastIspopeType1();
                if (null != optype) {
                    int ioptype = optype.intValue();
                    if (usjc) bsType = BusinessCat_Enum.ofFjJC(ioptype);
                    else if (ioptype >= 600) {
                        bsType = BusinessCat_Enum.values()[ioptype - 600];
                        ispBuilder.entrust(true);
                    } else bsType = BusinessCat_Enum.values()[ioptype];
                } else bsType = null;
                isp1 = ispBuilder.ispDate(null != bsd.getLastIspDate1() ? bsd.getLastIspDate1().toLocalDate() : null)
                        .conclusion(bsd.getLastIspConclu1()).bsType(bsType).build();
                //isp1==isp2导致Eqp.save()报错！Multiple representations of the same entity [md.specialEqp.inspect.Isp#2516] are being merged.Detached: Managed:
                iSPRepository.save(isp1);
            }
            if (StringUtils.hasText(bsd.getLastIspReport2())) {
                isp2 = iSPRepository.findTopByDevAndNo(eqp, bsd.getLastIspReport2());
                if (null == isp2) {
                    ispBuilder = new Isp().toBuilder().dev(eqp).no(bsd.getLastIspReport2());
                } else ispBuilder = isp2.toBuilder();
                Long optype = bsd.getLastIspopeType2();
                if (null != optype) {
                    int ioptype = optype.intValue();
                    if (usjc) bsType = BusinessCat_Enum.ofFjJC(ioptype);
                    else if (ioptype >= 600) {
                        bsType = BusinessCat_Enum.values()[ioptype - 600];
                        ispBuilder.entrust(true);    //委托的
                    } else if (40 == ioptype || 24 == ioptype)   //有问题: LAST_ISPOPE_TYPE2==40? 24？
                        bsType = BusinessCat_Enum.REGUL;
                    else bsType = BusinessCat_Enum.values()[ioptype];
                } else bsType = null;
                isp2 = ispBuilder.ispDate(null != bsd.getLastIspDate2() ? bsd.getLastIspDate2().toLocalDate() : null)
                        .conclusion(bsd.getLastIspConclu2()).bsType(bsType).build();
                iSPRepository.save(isp2);
                if (null != isp1 && isp2.getId().equals(isp1.getId()))
                    isp1 = null;   //同时new Isp()一次性作保存的，会报错Multiple representations being merged。
            }
        }
        String titl= "8".equals(eqp.getType())? bsd.getPipeBoxName() : bsd.getEqpName();
        eqp=eqpBld.svp(Tool.toJSONString(svp)).pa(Tool.toJSONString(pam)).subv(bsd.getSubEqpVart()).cert(bsd.getEqpUsecertCod())
                .model(bsd.getEqpMod()).cnam(bsd.getRegUserName()).titl(titl).rcod(bsd.getEqpRegCod())
                .regd(null!=bsd.getRegDate()? bsd.getRegDate().toLocalDate() : null).ocat(null!=jy && 2==jy.getInCag())
                .cand(null!=bsd.getRegLogoutDate()? bsd.getRegLogoutDate().toLocalDate() : null).isp1(isp1).isp2(isp2)
                .build();
        Division division=null, 检验部门=null;
        //使用单位的部门异常多，放在最后return。 优先采信旧检验平台,分支机构优先； 监察division数据更加不可信。分支机构本平台不能修改了。
        if(null!=eqp.getUseu()) {
            if (null != jy && null!=jy.getMgeDeptType()) {
                if(jy.getMgeDeptType()==2 && null != jy.getSecudeptId()){
                    UntSecudept untSecudept= untSecudeptRepository.findById(jy.getSecudeptId()).orElse(null);
                    if(null!=untSecudept){      //判断两个Long类型是否相等，Long==Long不好使！
                        if(eqp.getUseu()!= getUnitfromOldUnitId(false,untSecudept.getUNT_ID()))
                            return new Tuple("分支机构UNT_ID不对y:"+untSecudept.getUNT_ID(), eqp);
                        //某停用设备的:单位是南平市建阳区鼎盛物业管理有限公司,但分支机构建阳区万商红商贸城 应属于 南平市建阳区宝泰房地产开发有限公司{错配!},监察平台没分支机构=null。使用单位改了，但是分支没改掉。
                        division = divisionRepository.findByUnitAndName(eqp.getUseu(), untSecudept.getName());
                    }else
                        return new Tuple("没分支机构:"+jy.getSecudeptId(), eqp);
                }else if(jy.getMgeDeptType()==1 && null != jy.getSafeDeptId()){
                    UntDept untDept=untDeptRepository.findById(jy.getSafeDeptId()).orElse(null);
                    if(null!=untDept){
                        if(eqp.getUseu()!= getUnitfromOldUnitId(false,untDept.getUntId()))
                            return new Tuple("安全部门UNT_ID不对y:"+untDept.getUntId(), eqp);
                        division = divisionRepository.findByUnitAndName(eqp.getUseu(), untDept.getName());
                    }else
                        return new Tuple("没安全部门:"+jy.getSafeDeptId(), eqp);
                }
            }else if(null==jy && null!=jc){     //MGE_DEPT_TYPE 监察没有该字段！
                if(null != jc.getSecudeptId()){
                    JuntSecudept juntSecudept=juntSecudeptRepository.findById(jc.getSecudeptId()).orElse(null);
                    if(null!=juntSecudept){
                        if(eqp.getUseu()!= getUnitfromOldUnitId(true,juntSecudept.getUNT_ID(),false))
                            return new Tuple("分支机构UNT_ID不对c:"+juntSecudept.getUNT_ID(), eqp);
                        division = divisionRepository.findByUnitAndName(eqp.getUseu(), juntSecudept.getName());
                    }else
                        return new Tuple("没分支机构:"+jc.getSecudeptId(), eqp);
                }else if(null != jc.getSafeDept()){
                    JcuntDept jcuntDept=jcuntDeptRepository.findById(jc.getSafeDept()).orElse(null);
                    if(null!=jcuntDept){
                        if(eqp.getUseu()!= getUnitfromOldUnitId(true,jcuntDept.getUntId(),false))
                            return new Tuple("安全部门UNT_ID不对c:"+jcuntDept.getUntId(), eqp);
                        division = divisionRepository.findByUnitAndName(eqp.getUseu(), jcuntDept.getName());
                    }else
                        return new Tuple("没安全部门:"+jc.getSafeDept(), eqp);
                }
            }
        }else {
            retRes = "使用单位空";   //单位变更后我方同步延迟了,滞后@ 【数据变化快】，单位已经注销=null可检验平台单位id没动;  使用单位=注销态
            if(null!=bsd.getUseUntId()) {
                retRes += " c?" + usjc + "id=" + bsd.getUseUntId()+getFailOldUnitId映射(usjc,bsd.getUseUntId(),false);
            //【非正常数据】监察"报废"了当检验却是目录外在用。使用单位已经注销可是检验平台还能显示，检验平台分支机构和使用单位关联错误。
            }
        }
        if(null != jy && null!=jy.getEqpIspDept()) {
            HrDeptinfo  hrDeptinfo=hrDeptinfoRepository.findById(jy.getEqpIspDept()).orElse(null);
            if(null!=hrDeptinfo && null!=hrDeptinfo.getDivision()) {
                检验部门 = divisionRepository.findById(hrDeptinfo.getDivision()).orElse(null);
            }
        }
        eqp= eqp.toBuilder().usud(division).ispud(检验部门).build();
        //设备等级 只针对 【4000起重 游乐 管道】 这3个种/类才有输入;
        if("4".equals(eqp.getType()) )    eqp.setLevel(bsd.getEqpLevel());
        return new Tuple(retRes, eqp);
    }
    /**采信那个数据源-Eqp结构化字段要以JC优先同步来源，svp+pa非结构化字段以JY优先采信同步来源。
     *输入：pjc，jcb是监察的技术参数和基础表；pjy，jyb是旧检验的技术参数和基础表；
     * 特定于某设备种类的参数 ;电梯不需要level字段; 新添加？字段'防沉降组合：'
     * 返回值特殊！2个字段的；【错误信息，更新后的Eqp】
     * */
    public Tuple fillElevator(Elevator eqp,TelevPara pjc,TelevPara pjy,Bigetly jcb,Bigetly jyb) {
        String retRes ="";
        Elevator.ElevatorBuilder<?, ?>  eqpBld=eqp.toBuilder();
        TelevPara  pms= null==pjc? pjy:pjc;
        if(null==pms)  return new Tuple("没参数表", eqp);    //才5条
        eqpBld =eqpBld.flo(pms.getElefloornumber()).hlf(pms.getEleheight())
                .vl(pms.getRunvelocity()).nnor("是".equals(pms.getIfUnnormal())).cpm(pms.getConscrtype()).tm(pms.getTracangtype())
                .mtm(pms.getElecType()).buff(pms.getBufferMode()).rtl(pms.getRatedload()).aap(pms.getIfAdddevice()).prot(pms.getCarProtectType())
                .doop(pms.getDoorOpenType()).limm(pms.getRestspeedtype()).opm(pms.getControlType()).lesc(pms.getSlidwayUseLeng())
                .wesc(Tool.castFloat(pms.getNomiWidth()));
        if(null!=jyb) {     //仅在检验数据库才有的字段
            eqpBld =eqpBld.spec("1".equals(jyb.getIfSpecEqp())).oldb("1".equals(jyb.getIfOldbuildInst()))
                    .lbkd(null!=jyb.getLastBrakeTaskDate()? jyb.getLastBrakeTaskDate().toLocalDate() : null)
                    .nbkd(null!=jyb.getNextBrakeTaskDate()? jyb.getNextBrakeTaskDate().toLocalDate() : null);
        }
        pms= null==pjy? pjc:pjy;     //非关键字段的:优先采信旧检验。
        String oldsvp= eqp.getSvp();
        ElevatorSvp svp=JSON.parseObject(oldsvp, ElevatorSvp.class);
        String oldpa= eqp.getPa();
        ElevatorPam pam=JSON.parseObject(oldpa, ElevatorPam.class);
        Float  绳直径=null;
        Boolean  是钢带=false;
        if(StringUtils.hasText(pms.getDragDia())){
            String txt=pms.getDragDia();        //特殊分解：是钢带？.绳直径 ： 钢带规格
            if(txt.indexOf('*')>=0 || txt.indexOf('×')>=0 || txt.indexOf('X')>=0)   是钢带=true;
            if(!是钢带)    绳直径=Tool.castFloat(txt);
        }
        svp= svp.toBuilder().电动机类(pms.getElecStyle()).屏号(pms.getContscrcode()).曳引号(pms.getTracangleafacnumber())
                .主机号(pms.getElecCod()).电机功率(Tool.castFloat(pms.getElectropower())).电机转速(pms.getElecRev()).电梯门数(pms.getEledoornumber())
                .电梯站数(pms.getElestadenumber()).顶层高度(pms.getTopheight()).对重轨距(Tool.castFloat(pms.getCoupOrbDist())).对重块数(pms.getCoupNum())
                .额定载人(pms.getRatedPeople()).轿厢轨距(pms.getCarOrbDist()).速比(pms.getVPropor()).拖动(pms.getDragMode()).曳引比(pms.getDragPropor())
                .轮节径(Tool.castFloat(pms.getDragPitchDia())).绳数(pms.getDragNum()).上护型号(pms.getUpProtectType())
                .是钢带(是钢带).绳直径(绳直径).钢带规格(pms.getDragDia()).顶升形式(pms.getTopPatterns()).导轨型式(pms.getCounorbtype())
                .轿厢高(Tool.castFloat(pms.getCarHigh())).轿厢宽(Tool.castFloat(pms.getCarWidth()))
                .轿厢深(Tool.castFloat(pms.getCarDeep())).区域防爆(pms.getFbArealevel()).驱动方式(pms.getDrivApproach())
                .船梯("是".equals(pms.getIfShip())).公共交通("是".equals(pms.getIfPubTran())).汽车电梯("是".equals(pms.getIfCar()))
                .梯级宽度(pms.getRundlebreadth()).悬挂绳数(pms.getWireRopNum()).悬挂绳径(Tool.castFloat(pms.getWireRopDia()))
                .油缸数(Tool.castFloat(pms.getCylinderNum())).油缸形式(pms.getCylinderStyle())
                .防爆标志(pms.getFbMachineflag()).倾斜角度(Tool.castFloat(pms.getDipAngle())).泵功率(Tool.castFloat(pms.getPumpPower()))
                .build();
        pam= pam.toBuilder().上护编号(pms.getUpProtectCod()).钳型号(pms.getSafeclamtype()).钳编号(pms.getSafeclamnum()).底坑深度(pms.getBottomdepth())
                .补偿方式(pms.getCompentype()).对限速号(pms.getCoupLimitCod()).对限速型(pms.getCoupLimitType()).额定电流(pms.getRatedCurrent())
                .缓型号(pms.getBuffertype()).缓编号(pms.getBuffernumber()).缓厂家(pms.getBufferMakeUnt()).上限电速(pms.getCarUplimitEv())
                .上限机速(pms.getCarUplimitMv()).下限电速(pms.getCarDownlimitEv()).下限机速(pms.getCarDownlimitMv()).移护型(pms.getCarProtectType())
                .移护号(pms.getCarProtectCod()).锁型号(pms.getLockType()).手机信(!"否".equals(pms.getIfScanmobile()))
                .限速器号(pms.getRestspleafacnumber()).限绳直径(Tool.castFloat(pms.getLimitRopDia())).爆炸物质(pms.getFbSubstance())
                .上行额速(Tool.castFloat(pms.getUpRatedV())).下额定速(Tool.castFloat(pms.getDownRatedV())).限机械速(pms.getLimitMv()).泵编号(pms.getPumpCod())
                .泵流量(pms.getPumpFlux()).泵型号(pms.getPumpType()).泵转速(pms.getPumpSpeed()).液油型号(pms.getOilType()).防爆证号(pms.getFbHgcod())
                .build();
        if(null!=pjy){      //仅在检验数据库才有的字段
            pam= pam.toBuilder().层门型号(pjy.getFloordoortype()).装修(pjy.getCarDecorateSta())
                    .build();
            svp= svp.toBuilder().上护装置(pjy.getUpProtectModeandtype())
                    .build();
        }
        eqp=eqpBld.svp(Tool.toJSONString(svp)).pa(Tool.toJSONString(pam)).build();
        return new Tuple(retRes, eqp);
    }
    //返回异常消息:输入oid/cod,jc/jy,jcp/jyp 这3组合必须最少有一个数据。
    /**基础信息：监察中有的字段监察优先； 关键技术字段是监察优先；其它技术参数是检验优先。
     * 若oid<>'' oid+svu{JCUNT_ID盖过REG_UNT_ID默认福建省地市局};oid为空的看ispu{默认/地区}+cod作为搜索定位。
     * 极度例外只有OIDNO其它全空但是有EQP_AREA_COD+EQP_USECERT_COD。
     * */
    public String doSyncParmFromOld(String oid,String cod,Boolean usejc,Bigetly jc,Bigetly jy,ReadComm jcp,ReadComm jyp) {
        String retRes="";
        Bigetly base= usejc? jc : jy;    //尽量听监察的？
        //oid,cod 必然等于bigetly内容。
        Eqp eqp=null;
        Unit svu=null, ispu=null;
        //首先必须决定svu,ispu两个单位。 应当归属监察机构svu：
        Long desId=null;  //福建省局: 监察平台的id和检验平台的id一样吗？监察JCUNT_ID: "1"
        if(null!=base.getJcuntId())  desId=base.getJcuntId();
        else if(null!=base.getRegUntId())   desId=base.getRegUntId();
        else{
            String area=base.getEqpAreaCod().substring(0,4);
            if("3502".equals(area)) desId=4L;    //厦门的
            else if("3501".equals(area))    desId=2L;
            else if("3503".equals(area))    desId=5L;    //旧平台已有数据！
            else if("3504".equals(area))    desId=6L;
            else if("3505".equals(area))    desId=7L;
            else if("3506".equals(area))    desId=8L;
            else if("3507".equals(area))    desId=9L;
            else if("3508".equals(area))    desId=10L;
            else if("3509".equals(area))    desId=3L;
            else if("3515".equals(area))    desId=210L;
            else retRes="黑区域";      //5146条，外省
        }
        Jcdunit  jcdunit=jcdunitRepository.findByJcuntId(desId);
        if(null!=jcdunit && null!=jcdunit.getUunit())
            svu = units.findById(jcdunit.getUunit()).orElse(null);
        else if(UseState_Enum.DISCARD.ordinal() ==base.getEqpUseSta())   return "Nil:报废";   //1665条
        //正常的归属检验机构ispu：
        //TODO: 单位同步变更！导致缺省值已经修改了；
        desId=2L;  //默认！ 福建省特检院;    监察ISPUNT_ID: "2"； 旧检验平台没有字段IspuntId；可监察有可能实际改成外省其它检验机构了！！
        if(null!=jc && null!=jc.getIspuntId())  desId=jc.getIspuntId();
        else {
            String area = base.getEqpAreaCod().substring(0, 4);
            if ("3502".equals(area))
                desId = 28L;  //"28""厦门市特种设备检验检测院"; 旧平台已有数据！  ISPUNT_ID:
        }
        //todo:检验获得的base of jy:desId id映射 一样吗?
        JispUnit jispUnit=jispUnitRepository.findByIspuntId(desId);
        if(null!=jispUnit && null!=jispUnit.getUunit())
            ispu= units.findById(jispUnit.getUunit()).orElse(null);
        if(null!=svu && StringUtils.hasText(oid)){
              //从数据库找到现成的Eqp:(oid,svu)都要配对。
            eqp = eQPRepository.findByOidAndSvu(oid, svu);  //找出多个设备=抛出异常!分片作业停止了;
        }
        else if(null!=ispu && StringUtils.hasText(cod)){
            //从数据库找到现成的Eqp:(cod,ispu)都要配对。
            eqp= eQPRepository.findByCodAndIspu(cod,ispu);
        }
        else {
            if(StringUtils.hasText(oid) && null==svu)   return "无svu";      //只有一条:已注销拆除
            else if(StringUtils.hasText(cod) && null==ispu)  return "无ispu";
            else  return "无码查:"+oid+":"+cod;
        }
        String type="", sort="", vart="";    //最少提前获知字段：
        //新 需要导入的设备
        if(null==eqp){
            //只能新生成个Eqp:
            eqp=new Eqp();
            if(!StringUtils.hasText(base.getEqpType()))   return "EQP_TYPE空";
            type=base.getEqpType().substring(0,1);
            Eqp.EqpBuilder<?, ?>  eqpBld=null;
            if(type.equals("3"))
                eqpBld = Elevator.builder();
            else if(type.equals("2") || type.equals("R"))
                eqpBld = Vessel.builder();
            else if(type.equals("1"))
                eqpBld = Boiler.builder();
            else if(type.equals("4"))
                eqpBld = Crane.builder();
            else if(type.equals("5"))
                eqpBld = FactoryVehicle.builder();
            else if(type.equals("6"))
                eqpBld = Amusement.builder();
            else if(type.equals("8"))
                eqpBld = Pipeline.builder();
            else if(type.equals("9"))
                eqpBld = Ropeway.builder();
            else
                eqpBld = Eqp.builder();
            if(StringUtils.hasText(base.getEqpSort()))   sort=base.getEqpSort().substring(0,2);
            if(StringUtils.hasText(base.getEqpVart()))   vart=base.getEqpVart().substring(0,3);
            if(null==base.getEqpUseSta()){
                if(null!=jy &&  7==jy.getEqpUseSta())    return "Nil:ust垃圾";
                else if( 0==base.getEqpRegSta())    return "Nil:ust丢弃历史";
                else if(StringUtils.hasText(base.getEqpUseAddr()) && base.getEqpUseAddr().length()>8)    return "Nil:ustTa";
                else    return "Nil:ustT";
            }
            //svu ispu 初始化后若修改 oid->要改svu, cod->要改动ispu需要删除旧的数据啊：不然重复的oid, cod！本平台允许oid cod对于正常逻辑允许重复，同步外部数据要例外处置。
            eqp =eqpBld.type(type).oid(oid).cod(cod).svu(svu).ispu(ispu).sort(sort).vart(vart)
                        .reg(RegState_Enum.values()[base.getEqpRegSta()]).ust(UseState_Enum.values()[base.getEqpUseSta()])
                        .build();
            try {
                eQPRepository.saveAndFlush(eqp);
            } catch (Exception e) {
                e.printStackTrace();
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return "flush败";
            }
        /*  拆分两个阶段: 批处理：批量保存同步给ES;
            EqpEs eqpEs=eqp.refreshEsIndex(null);
            //String json=JSON.toJSONString(eqp)会得到暴大的内容，！！整个都装进来？？eqpEs=JSON.parseObject(json, EqpEs.class);
            eqpEsRepository.save(eqpEs);
            */
        }
        else{   //已经存在设备， 不能修改svu ispu了，除非先删除掉，同步外部数据局限。
            if(svu!=eqp.getSvu())   return "svu改动";
            if(ispu!=eqp.getIspu())   return "ispu改动";    //针对委托设备只有cod没有oid+svu;
            //若cod oid 代码不为空&&不相等的判定=是在前面运行的逻辑,上级getDeviceFromOld()就过滤掉。
            if(!StringUtils.hasText(eqp.getCod()) && StringUtils.hasText(cod) )
                eqp.setCod(cod);
            if(!StringUtils.hasText(eqp.getOid()) && StringUtils.hasText(oid) )
                eqp.setOid(oid);     //[同步旧平台数据] oid不能从有的变身空的:后退
        }
        type=eqp.getType();
        sort=eqp.getSort();
        Tuple retv=fillEqpBase(eqp,usejc, jc, jy);      //非新设备极个别字段oid cod svu ispu不能修改的。
        retRes+= retv.getVal1();
        eqp= (Eqp) retv.getVal2();
        retv=null;
        if("3".equals(type)){
            TelevPara parjc=null, parjy=null;
            if(null!=jcp.getArr() && 1==jcp.getArr().size())
                parjc =(TelevPara)jcp.getArr().get(0);
            if(null!=jyp.getArr() && 1==jyp.getArr().size())
                parjy =(TelevPara)jyp.getArr().get(0);
            retv= fillElevator((Elevator)eqp, parjc,parjy, jc,jy);
        }
        else if("2".equals(type) || "R".equals(type)){
            VesselPrm parjc=null, parjy=null;       //常压容器"R"-检验的技术表不一样！
            if(null!=jcp.getArr() && 1==jcp.getArr().size())
                parjc =(VesselPrm)jcp.getArr().get(0);
            if(null!=jyp.getArr() && 1==jyp.getArr().size())
                parjy =(VesselPrm)jyp.getArr().get(0);
            retv= "R".equals(type)? fillVessel常((Vessel)eqp, parjc,parjy, jc,jy) : fillVessel((Vessel)eqp, parjc,parjy, jc,jy);
        }
        else if("4".equals(type) )
        {
            PcraneQzj parjc=null;
            if(null!=jcp.getArr() && 1==jcp.getArr().size())
                parjc =(PcraneQzj)jcp.getArr().get(0);
            if("48".equals(sort) ){
                IlifsParg parjy=null;   //升降机4800 旧检验优待，独立出来 技术表; 前端也是分开的。
                if(null!=jyp.getArr() && 1==jyp.getArr().size())
                    parjy =(IlifsParg)jyp.getArr().get(0);
                retv= fillCrane升降机((Crane)eqp, parjc,parjy, jc,jy);
            }else {
                PcraneQzj parjy=null;
                if(null!=jyp.getArr() && 1==jyp.getArr().size())
                    parjy =(PcraneQzj)jyp.getArr().get(0);
                retv= fillCrane((Crane)eqp, parjc,parjy, jc,jy);
            }
        }
        else if("1".equals(type)){
            BoilerDat parjc=null, parjy=null;
            if(null!=jcp.getArr() && 1==jcp.getArr().size())
                parjc =(BoilerDat)jcp.getArr().get(0);
            if(null!=jyp.getArr() && 1==jyp.getArr().size())
                parjy =(BoilerDat)jyp.getArr().get(0);
            retv= fillBoiler((Boiler)eqp, parjc,parjy, jc,jy);
        }
        else if("5".equals(type)){
            CarDrive parjc=null, parjy=null;
            if(null!=jcp.getArr() && 1==jcp.getArr().size())
                parjc =(CarDrive)jcp.getArr().get(0);
            if(null!=jyp.getArr() && 1==jyp.getArr().size())
                parjy =(CarDrive)jyp.getArr().get(0);
            retv= fillFactoryVehicle((FactoryVehicle)eqp, parjc,parjy, jc,jy);
        }
        else if("6".equals(type)){
            YoxiPamj parjc=null, parjy=null;
            if(null!=jcp.getArr() && 1==jcp.getArr().size())
                parjc =(YoxiPamj)jcp.getArr().get(0);
            if(null!=jyp.getArr() && 1==jyp.getArr().size())
                parjy =(YoxiPamj)jyp.getArr().get(0);
            retv= fillAmusement((Amusement)eqp, parjc,parjy, jc,jy);
        }
        else if("8".equals(type)){
            //[特别] 3509D100619,DJ00851,不一致cod=3509D585 不会运行到这; 检验平台有两个cod代码，监察平台管道单元数据更多。
            LineNotsen parjc=null, parjy=null;
            if(null!=jcp.getArr() && 1==jcp.getArr().size())
                parjc =(LineNotsen)jcp.getArr().get(0);
            if(null!=jyp.getArr() && 1==jyp.getArr().size())
                parjy =(LineNotsen)jyp.getArr().get(0);
            retv= fillPipeline((Pipeline)eqp, parjc,parjy, jc,jy);
            eqp= (Eqp) retv.getVal2();
            if(eqp.getReg()!=RegState_Enum.CANCEL) {     //不一致，无法决定该采信JC还是JY，？监察平台优先:
                retRes += retv.getVal1();
                ReadComm jcls = new ReadComm<>();    //监察单元表
                ReadComm jyls = new ReadComm<>();
                String conds;
                if (StringUtils.hasText(eqp.getOid())) {     //过滤后注册登记码是唯一的||空的；
                    conds = "OIDNO='" + eqp.getOid() + "' AND USE_STA<>7 AND USE_STA<>8 AND USE_STA<>5 AND USE_STA<>4";
                    jcls = Utilities.getItArrType(DyguanDao.class, 224, conds);
                    if (null == jcls || null == jcls.getArr())  return "读单元失败c";
                }
                if (StringUtils.hasText(eqp.getCod())) {      //两个平台看见的单元数和状态有很多不一致的，合并后会更多条数！
                    conds = "EQP_COD='" + eqp.getCod() + "' AND USE_STA<>7 AND USE_STA<>8 AND USE_STA<>5 AND USE_STA<>4";
                    jyls = Utilities.getItArrType(DyguanDao.class, 24, conds);
                    if (null == jyls || null == jyls.getArr())  return "读单元失败y";
                }
                retv = syncPipeDevice((Pipeline) eqp, jcls.getArr(), jyls.getArr());
            }
        }     //设备类= 7个+ 管道单元特别的！
        if(null!=retv){
            retRes+= retv.getVal1();   //返回值数组包装【错误信息，变更的设备Eqp】
            eqp= (Eqp) retv.getVal2();    //eqp可是出现变化的。
        }
       //return("9000".equals(bigetly.getEqpType()) ? "设备类暂弃" : "设备类无"+bigetly.getEqpType());
        //这个时间eqp.isp1.id==eqp.isp2.id就会报错！同一个会话中后面new Isp()覆盖前面的isp1。
        eQPRepository.save(eqp);
        return retRes;   //为何退出时刻能够自动保存Eqp的修改。？？
    }
    /**从旧平台获取某个设备信息+参数； 监察OIDNO是JC_EQP_MGE唯一识别码；检验EQP_COD是TB_EQP_MGE唯一识别码；
     * return有提示异常信息的,不代表一定没有生成Eqp和EqpEs数据。 不应该依靠临时申请表：(属于监察功能的范围=申请注册信息)
     * 【注意】fail字段加大；
     * */
    public String getDeviceFromOld(String cod,String oid, String threadKey) {
        String retRes="";
        log.info("{}设备:cod{},oid{}",threadKey,cod,oid);
        if(!StringUtils.hasText(oid) && !StringUtils.hasText(cod))     return("空");
        ReadComm<Bigetly> ojc=new ReadComm<Bigetly>();   //监察正式的
        ReadComm<Bigetly> cjy=new ReadComm<Bigetly>();   //检验平台的
        Bigetly  trfjc=null, trfjy=null;
        String cond="";
        if(StringUtils.hasText(oid)){
            cond= "OIDNO='" +oid+ "'";
            ojc= Utilities.getItArrType(Bigetly.class,201,cond);        //监察的
            if(null==ojc || null==ojc.getArr() )       return("读实体失败1");
            else if(ojc.getArr().size()>0)      trfjc=ojc.getArr().get(0);
        }
        if(StringUtils.hasText(cod)){
            cond="EQP_COD='" +cod+ "'";
            cjy= Utilities.getItArrType(Bigetly.class,1,cond);       //检验的
            if(null==cjy || null==cjy.getArr() )       return("读实体失败2");
            else if(cjy.getArr().size()>0)       trfjy=cjy.getArr().get(0);
        }
        if(null==trfjc && null==trfjy)   return("没找到数据");    //=72条 厦门;
        else if(null!=trfjc && null!=trfjy){       //排除两个平台不一致数据
            if(StringUtils.hasText(trfjc.getEqpCod()) && StringUtils.hasText(cod) && !cod.equals(trfjc.getEqpCod()))
                return ("不一致cod="+trfjc.getEqpCod());     //有114条
            if(StringUtils.hasText(trfjy.getOidno()) && StringUtils.hasText(oid) && !oid.equals(trfjy.getOidno())) {
                if(null!=trfjc.getEqpRegSta() && 3==trfjc.getEqpRegSta() )    return ("注销oid改" + trfjy.getOidno());
                else  return ("不一致oid=" + trfjy.getOidno());     //就2条 ?同步时间引起滞后不一致
            }
        }
        //监察正式表 认定为正常状态的 才能采信监察数据。否则相信检验平台的。
        Boolean  usejcbase= (trfjc!=null);     //usejcbase代表监察正式表找到，未注册||删除设备就没了。
        if(usejcbase && null!=trfjy && null!=trfjc.getEqpRegSta() && 3==trfjc.getEqpRegSta() )
            usejcbase=false;        //注销登记 reg==3
        Bigetly  bigetly= usejcbase? trfjc: trfjy;
        if(null==bigetly)   return("读实体没法用");
        //无效数据舍弃： .ust(UseState_Enum.values()[ol.getEQP_USE_STA()]).    EQP_USE_STA.eq(Byte.valueOf("2"))
        if(null!=bigetly.getEqpUseSta() && 7==bigetly.getEqpUseSta() ){
            return("ust垃圾数据");    //29033条
        }
        ReadComm    jcparm=new ReadComm<>();    //监察技术参数
        ReadComm    jyparm=new ReadComm<>();
        String oidcd ="OIDNO='"+oid+"'";
        String codcd ="EQP_COD='"+cod+"'";
        if("3000".equals(bigetly.getEqpType())){
            //jishu= pamusejc?  useTmp? "JC_TEMP_ELEV_PARA":"JC_ELEV_PARA" : "TB_ELEV_PARA";
            if(usejcbase && StringUtils.hasText(oid)) {       //临时设备就别去监察找了
                jcparm = Utilities.getItArrType(TelevPara.class, 202, oidcd);
                if(null==jcparm || null==jcparm.getArr())   return "读参数失败c";
            }
            if(StringUtils.hasText(cod)) {
                jyparm = Utilities.getItArrType(TelevPara.class, 2, codcd);
                if(null==jyparm || null==jyparm.getArr())   return "读参数失败y";     //竟然 HttpClientService-line: 97, errorMsgGET请求失败！"jyparm" is null:暂停!
            }
        }
        else if("2000".equals(bigetly.getEqpType()) || "R000".equals(bigetly.getEqpType())){
            //R000 罐车 目录外： "R000": "TB_GENERAL_VESSEL_PARA"  监察没有区分"R000"
            if(usejcbase && StringUtils.hasText(oid)) {
                jcparm = Utilities.getItArrType(VesselPrm.class, 203, oidcd);
                if(null==jcparm || null==jcparm.getArr())   return "读参数失败c";
            }
            if(StringUtils.hasText(cod)) {
                if("R000".equals(bigetly.getEqpType()))      //常压罐车@检验平台 单独的技术参数表
                    jyparm = Utilities.getItArrType(VesselPrm.class, 23, codcd);
                else
                    jyparm = Utilities.getItArrType(VesselPrm.class, 3, codcd);
                if(null==jyparm || null==jyparm.getArr())   return "读参数失败y";
            }
        }
        else if("4000".equals(bigetly.getEqpType()) )
        {
            if(usejcbase && StringUtils.hasText(oid)) {
                jcparm = Utilities.getItArrType(PcraneQzj.class, 204, oidcd);
                if(null==jcparm || null==jcparm.getArr())   return "读参数失败c";
            }
            if(StringUtils.hasText(cod)) {
                if ("4800".equals(bigetly.getEqpSort())) {    //检验平台，拆分出单独的技术参数表
                     jyparm = Utilities.getItArrType(IlifsParg.class, 9, codcd);
                    //jishu= pamusejc?  useTmp? "JC_TEMP_CRANE_PARA":"JC_CRANE_PARA" : "TB_LIFT_PARA";
                } else {
                     jyparm = Utilities.getItArrType(PcraneQzj.class, 4, codcd);
                }
                if(null==jyparm || null==jyparm.getArr())  return "读参数失败y";
            }
        }
        else if("1000".equals(bigetly.getEqpType())){
            if(usejcbase && StringUtils.hasText(oid)) {
                jcparm = Utilities.getItArrType(BoilerDat.class, 205, oidcd);
                if(null==jcparm || null==jcparm.getArr())   return "读参数失败c";
            }
            if(StringUtils.hasText(cod)) {
                jyparm = Utilities.getItArrType(BoilerDat.class, 5, codcd);
                if(null==jyparm || null==jyparm.getArr())   return "读参数失败y";
            }
        }
        else if("5000".equals(bigetly.getEqpType())){
            if(usejcbase && StringUtils.hasText(oid)) {
                jcparm = Utilities.getItArrType(CarDrive.class, 206, oidcd);
                if(null==jcparm || null==jcparm.getArr())   return "读参数失败c";
            }
            if(StringUtils.hasText(cod)) {
                jyparm = Utilities.getItArrType(CarDrive.class, 6, codcd);
                if(null==jyparm || null==jyparm.getArr())   return "读参数失败y";
            }
        }
        else if("6000".equals(bigetly.getEqpType())){
            if(usejcbase && StringUtils.hasText(oid)) {
                jcparm = Utilities.getItArrType(YoxiPamj.class, 207, oidcd);
                if(null==jcparm || null==jcparm.getArr())   return "读参数失败c";
            }
            if(StringUtils.hasText(cod)) {
                jyparm = Utilities.getItArrType(YoxiPamj.class, 7, codcd);
                if(null==jyparm || null==jyparm.getArr())   return "读参数失败y";
            }
        }
        else if("8000".equals(bigetly.getEqpType())){
            if(usejcbase && StringUtils.hasText(oid)) {
                jcparm = Utilities.getItArrType(LineNotsen.class, 208, oidcd);
                if(null==jcparm || null==jcparm.getArr())   return "读参数失败c";
            }
            if(StringUtils.hasText(cod)) {
                jyparm = Utilities.getItArrType(LineNotsen.class, 8, codcd);
                if(null==jyparm || null==jyparm.getArr())   return "读参数失败y";
            }
        }
        else {
            //检验没有"TB_CABLEWAY_PARA", 9000设备=暂放弃。 :设备类暂弃=14条；设备类无=0条；
            return("9000".equals(bigetly.getEqpType()) ? "设备类暂弃" : "设备类无"+bigetly.getEqpType());
        }
        //厦门的？bigetly.getIspuntId()==28L ；  4L== bigetly.getRegUntId())     //厦门监察=4
        //实际数据httpClient查询失败的不会执行下一句的，直接向上级函数再抛出异常！需要外部catch异常。
        return doSyncParmFromOld(oid,cod,usejcbase,trfjc,trfjy,jcparm,jyparm);
    }
    /**单一条 Unit的恢复：优先把旧检验单位表导入Unit，后面才是监察旧表导入。
     * */
    public <T> Triple  constructUnit_Su检验批Jy(T inObj) {
        String retRes="";
        CompanyEs companyEs=null;
        PersonEs personEs=null;
        UntMge untMge= (UntMge)inObj;    //？？泛型==欺骗编译器;
        //log.info("分片导入检验Unit:UntMge {}", untMge.getUNT_ID()); 目标分片的依照表=UntMge，全部的记录，缺省是没顺序:有问题，新插入的怎么办？。
        if(!StringUtils.hasText(untMge.getUNT_NAME()) )    return new Triple("nameNull", null, null);
        //if(2==untMge.getUNT_STATE())   return new Triple("注销态", null, null);
        if(!StringUtils.hasText(untMge.getUNT_ORG_COD()) )  return new Triple("no空", null, null);
        //旧平台区分不明确！[判定可能是]是个人吗? 没有很好自动判定个人企业办法，但求原则是一致。多次同步保证一样的分辨！
        Unit unit;
        Adminunit adminunit=adminunitRepository.findTopByAreacode(untMge.getUNT_AREA_COD());
        boolean isPerson= "Z01".equals(untMge.getINDUSTRY_PROP_COD()) || untMge.getUNT_NAME().length()<=3 ||
                   (untMge.getUNT_ORG_COD().length()==18 && untMge.getUNT_ORG_COD().startsWith("35"));
        if(!isPerson)
        {
            Company company= companies.findByName(untMge.getUNT_NAME());
            if(null!=company){
                unit= units.findUnitByCompany(company);
                if(null==unit)  return new Triple("unitNull", null, null);
                if(2==untMge.getUNT_STATE()) {
                    untMge.setUunit(unit.getId());
                    return new Triple("注销同名", null, null);
                }
            }else{
                company=Company.builder().name(untMge.getUNT_NAME()).no(untMge.getUNT_ORG_COD())
                        .build();
                unit=Unit.builder().build();
            }
            //多数属性可再次覆盖：但关键字段 name no 还是不要变更了。
            company= company.toBuilder().address(untMge.getUNT_ADDR()).linkMen(untMge.getUNT_LKMEN())
                    .phone(untMge.getUNT_MOBILE()).tel(untMge.getUNT_PHONE()).ad(adminunit)
                    .lat(untMge.getUNT_LAT()).lon(untMge.getUNT_LONG()).build();
            unit= unit.toBuilder().company(company).indCod(untMge.getINDUSTRY_PROP_COD()).area(untMge.getUNT_AREA_COD())
                    .cancel(2==untMge.getUNT_STATE())
                    .build();
            try {
                companies.save(company);
                units.save(unit);
            } catch (Exception e) {
                e.printStackTrace();
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return new Triple("保存Fail", null, null);
            }
            companyEs=new CompanyEs();
            BeanUtils.copyProperties(company,companyEs);
        }
        else{       //个人 身份证 是唯一性定位码
            Person person= persons.findByNo(untMge.getUNT_ORG_COD());
            if(null!=person){
                unit= units.findUnitByPerson(person);
                if(null==unit)  new Triple("unitNull", null, null);
                if(2==untMge.getUNT_STATE()) {
                    untMge.setUunit(unit.getId());
                    return new Triple("注销同码", null, null);
                }
            }else{
                person=Person.builder().name(untMge.getUNT_NAME()).no(untMge.getUNT_ORG_COD())
                        .build();
                unit=Unit.builder().build();
            }
            //个人只有手机，没有联系人。adminunit当前流动居住地；lat+lon; 关键字段 name no不能再次变更。
            person= person.toBuilder().address(untMge.getUNT_ADDR())
                    .phone((null==untMge.getUNT_MOBILE() || untMge.getUNT_MOBILE().length()<8)? untMge.getUNT_PHONE() : untMge.getUNT_MOBILE())
                    .ad(adminunit).lat(untMge.getUNT_LAT()).lon(untMge.getUNT_LONG()).build();
            unit= unit.toBuilder().person(person).indCod(untMge.getINDUSTRY_PROP_COD()).area(untMge.getUNT_AREA_COD())
                    .cancel(2==untMge.getUNT_STATE())
                    .build();
            try {
                persons.save(person);
                units.save(unit);
            } catch (Exception e) {
                e.printStackTrace();
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return new Triple("保存Fail", null, null);
            }
            personEs=new PersonEs();
            BeanUtils.copyProperties(person,personEs);
        }
        untMge.setUunit(unit.getId());   //事务当中查询出来的数据库实体：是可以自动保存的,new()不算。
        return new Triple(retRes, companyEs, personEs);   //2个需做批量保存的仓库实体
    }
    /**监察的单位优先覆盖检验的：    针对性添加索引，提速7倍了；绝大多数数据库负载是在非主键的查询过滤上的。
     * 返回非空的三元组<结果; 公司ES; 个人ES>, 目的是ES做批量保存;
     * */
    public <T> Triple constructUnit_Su监察批cM(T inObj) {
        String retRes="";
        CompanyEs companyEs=null;
        PersonEs personEs=null;
        JcUntMge untMge= (JcUntMge)inObj;
        if(!StringUtils.hasText(untMge.getUNT_NAME()) )    return new Triple("nameNull", null, null);
        //if(2==untMge.getUNT_STATE())   return new Triple("注销态", null, null);
        if(!StringUtils.hasText(untMge.getUNT_ORG_COD()) )  return new Triple("no空", null, null);
        Unit unit;
        Adminunit adminunit=adminunitRepository.findTopByAreacode(untMge.getUNT_AREA_COD());
        boolean isPerson= "Z01".equals(untMge.getINDUSTRY_PROP_COD()) || untMge.getUNT_NAME().length()<=3 ||
                (untMge.getUNT_ORG_COD().length()==18 && untMge.getUNT_ORG_COD().startsWith("35"));
        if(!isPerson)
        {
            QCompany qm = QCompany.company;
            BooleanBuilder builder = new BooleanBuilder();
            builder.and(qm.name.eq(untMge.getUNT_NAME()));
            Company company= companies.findOne(builder).orElse(null);
            if(null!=company){
                unit= units.findUnitByCompany(company);
                if(null==unit)  return new Triple("unitNull", null, null);    //不允许unit直接丢弃关联的Company或Person
                if(2==untMge.getUNT_STATE()) {
                    untMge.setUunit(unit.getId());
                    return new Triple("注销同名", null, null);
                }
            }else{
                company=Company.builder().name(untMge.getUNT_NAME()).no(untMge.getUNT_ORG_COD())
                        .build();
                unit=Unit.builder().build();
            }
            company= company.toBuilder().address(untMge.getUNT_ADDR()).linkMen(untMge.getUNT_LKMEN())
                    .phone(untMge.getUNT_MOBILE()).tel(untMge.getUNT_PHONE()).ad(adminunit)
                    .lat(untMge.getUNT_LAT()).lon(untMge.getUNT_LONG()).build();
            unit= unit.toBuilder().company(company).indCod(untMge.getINDUSTRY_PROP_COD()).area(untMge.getUNT_AREA_COD())
                    .cancel(2==untMge.getUNT_STATE())
                    .build();
            try {
                companies.save(company);
                units.save(unit);
            } catch (Exception e) {
                e.printStackTrace();
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return new Triple("保存Fail", null, null);
            }
            companyEs=new CompanyEs();
            BeanUtils.copyProperties(company,companyEs);
        }
        else{
            QPerson qm = QPerson.person;
            BooleanBuilder builder = new BooleanBuilder();
            builder.and(qm.no.eq(untMge.getUNT_ORG_COD()));
            Person person= persons.findOne(builder).orElse(null);
            if(null!=person){
                unit= units.findUnitByPerson(person);
                if(null==unit)  return new Triple("unitNull", null, null);
                if(2==untMge.getUNT_STATE()) {
                    untMge.setUunit(unit.getId());
                    return new Triple("注销同码", null, null);
                }
            }else{
                person=Person.builder().name(untMge.getUNT_NAME()).no(untMge.getUNT_ORG_COD())
                        .build();
                unit=Unit.builder().build();
            }
            person= person.toBuilder().address(untMge.getUNT_ADDR())
                    .phone((null==untMge.getUNT_MOBILE() || untMge.getUNT_MOBILE().length()<8)? untMge.getUNT_PHONE() : untMge.getUNT_MOBILE())
                    .ad(adminunit).lat(untMge.getUNT_LAT()).lon(untMge.getUNT_LONG()).build();
            unit= unit.toBuilder().person(person).indCod(untMge.getINDUSTRY_PROP_COD()).area(untMge.getUNT_AREA_COD())
                    .cancel(2==untMge.getUNT_STATE())
                    .build();
            try {
                persons.save(person);
                units.save(unit);
            } catch (Exception e) {
                e.printStackTrace();
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return new Triple("保存Fail", null, null);
            }
            personEs=new PersonEs();
            BeanUtils.copyProperties(person,personEs);      //返回存入Triple批量保存
        }
        untMge.setUunit(unit.getId());       //事务当中查询出来的数据库实体：是可以自动保存的,new()不算。
        return new Triple(retRes, companyEs, personEs);   //2个需做批量保存的仓库实体
    }
    /**jobm内容返回时可能改变： offs=从0开始, seqNO=offs+limit;
     * */
    public <T> Triple<StoreSyncRepository,Object,Object>  syncEqp单设备OD并发(T inObj, String arg) {
        StoreSync parb= (StoreSync)inObj;
        String result=getDeviceFromOld(parb.getCod(),parb.getOid(), arg);
        return new Triple(result, null, null);
    }
    //改成批量存ES；可以节省掉88.7%时间，提速8倍！惊人的批处理性能。
    //@Transactional(propagation = REQUIRES_NEW) 对于CRDB没有用，不能嵌套事务，只需要注解最外层函数一次。
    public JsliceMang constructUnitSu(JsliceMang jobm,String arg) {
        Function<SliceSyncRes, Triple>  sliceJob= parb ->{
            if("cM".equals(arg))    return constructUnit_Su监察批cM(parb);
            else if("Jy".equals(arg))    return constructUnit_Su检验批Jy(parb);
            else if("Pm".equals(arg))    return constructUnit_Su监察批Pm(parb);
            else return null;
        };
        if("cM".equals(arg)){
            if(!emIncp.isJoinedToTransaction())      emIncp.joinTransaction();
            return sliceJobBatch(jobm,jcUntMgeRepository, sliceJob, companyEsRepository, personEsRepository);
        }else if("Jy".equals(arg)){
            //CRDB集群，emFjtj emSei 几个都是同一个数据库集群的；全局 事务还需要分开管理者吗？
            if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
            return sliceJobBatch(jobm,untMgeRepository, sliceJob, companyEsRepository, personEsRepository);
        }else if("Pm".equals(arg)){
            //内层函数加@Transactional 会报错 No EntityManager with actual transaction available for current thread - cannot reliably process 'joinTransaction'
            //外层函数加@Transactional的必须加上：才会保存incp数据库的。 没上joinTransaction报错: no transaction is in progress | 不会保存
            if(!emIncp.isJoinedToTransaction())      emIncp.joinTransaction();       //没添加joinTransaction就不会保存数据库的。
            return sliceJobBatch(jobm,jcPermtUntRepository, sliceJob, null, null);
        }
        else return null;   //泛型决定parb最终类型。 sliceJob是嵌入在Loop内部的函数块。
    }
    //分片作业，楼盘,Etc等； PagingAndSortingRepository
    public <T extends SliceSyncRes & PagingAndSortingRepository<T,?> & CrudRepository<T,?>> Boolean buildVillageEtcVF(JsliceMang jobm,String arg) {
        T  repository=null;
        if("vL".equals(arg)){
            if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
            Assert.isTrue(emFjtj.isJoinedToTransaction(),"没emFjtj Transaction");
            repository= (T) houseMgeRepository;
        }else if("jS".equals(arg)){     //代码看前端的命令页面"jF"
            if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
            repository= (T) untSecudeptRepository;
        }else if("jD".equals(arg)){
            if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
            repository= (T) untDeptRepository;
        }else if("cS".equals(arg)){
            if(!emIncp.isJoinedToTransaction())      emIncp.joinTransaction();
            repository= (T) juntSecudeptRepository;
        }else if("cD".equals(arg)){
            if(!emIncp.isJoinedToTransaction())      emIncp.joinTransaction();
            repository= (T) jcuntDeptRepository;
        }
        T finalRepository = repository;
        Function<SliceSyncRes, Boolean>  sliceJob= parb ->{
            String result= buildVillageEtc单个VF(parb, arg);
            if(StringUtils.hasText(result) ){
                finalRepository.<T>save((T) parb);
            }
            return true;
        };
        return jobm.run(repository, sliceJob);   //泛型决定parb最终类型。
    }
    //新的模式：分片维护作业的入口： 前端可并发点击，就算同一个浏览页面也会两次点击一个按钮导致2个线程2个事务冲突。
    /**pasw参数：authID 或者是'密码password'的形式,如果是后者分片作业就初始启动直接返回。
     * 前提：极端下确保1分钟内必须完成一个分片作业。一个客户可能连续占用，无法抢任务。
     * 缺点是必须人工网页开着才能发起后台作业。
     * syncEqpFromLegacy_AU()子函数需保存Repository不是主数据库的！必须controller入口加@Transactional才能真正保存数据。
     * CRDB数据库表现：会锁住表，查询整表或count都会被拖延的。小强数据库默认就是串行化的事务，应用层不需要处理并发冲突。和MySql逻辑不同。
     * (非分片)太大的表拆分成多次操作：arg参数进行扩展？"Jy:$idbegin1,$idend2"解析出分片:ID">起点&&<=终点"范围，非分片不用offset。
     * 这个启点不应该添加事务： 事务往内部方法移动。
     * 只能在graphQL直接对接的接口函数外层函数上加@Transactional才能确保不会- no Session报错。除非把FetchType.LAZY改成EAGER; 不能不加@Transactional来延长session周期
     * */
    @Deprecated
    public JsliceMang syncEqpFromLegacy外套模式(String type,String arg,String pasw,int offset) {
        int numCalls = 0;
        //这样允许分解多个步骤，不是同一个Transactional单一事务之内进行的。 最上面这层函数加@Transactional还是必须的。
        JsliceMang jsliceMang=null;
        do {
            numCalls++;
            try {
                //下面这句也会抛出异常然后直接返回给前端的，没有执行剩下的控制逻辑！
                jsliceMang=context.getBean(MaintenanceMutation.class).syncEqpFromLegacy(type, arg, pasw, offset);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if(null==jsliceMang){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }       //导致其它种类错误 也会重复，最后还不会抛出异常给前端！
        } while (null==jsliceMang && numCalls < 20);
        //乱catch()引起逻辑异常：这样会导致连续20个UserAccessDenyException: 没登录； 但就是返回给前端null!
        //【差别大】不能直接用 return syncFromLegacyInner(type, arg, pasw, offset);
        return jsliceMang;
    }
    //@Transactional(rollbackFor = Exception.class) 若是取消@Transactional立刻都能保存提交DB;
    /**
     * 若 @Transactional 注解在内部函数，graphQL会报错。 因为return JsliceMang对象的内省User是Lazy Fetch的，所以可能还没有User数据您就return了。
     * 这种情况：根据返回的JsliceMang以及前端内省实际需要字段，@Transactional应该添加到graphQL直接对接的接口函数外层函数上面。
     * 内层外层函数两个地方都不加@Transactional也是报错的。仅仅加在内层函数也是报错的could not initialize proxy  - no Session。 解决no Session办法：
     * 老办法open-in-view=true必须把@Transactional加到graphQL直接对接的接口函数外层函数上，否则JsliceMang没有做save直接return的就不会保存到数据库。
     * Transactional默认针对RuntimeException|Error回滚的,写写冲突时刻，无法确保offs不会在异常抛出JpaSystemException后回滚，就是offs还是会往前走。
     * Transactional(rollbackFor = Exception.class)添加应用定义异常回滚, rollbackFor默认只对RuntimeException回滚， rollbackFor加上终于可以确保JsliceMang回滚。
     * Transactional Fjtj数据库的表参与事务出现了写冲突：syncEqpFromLegacy返回提交时刻才会捕捉到异常的。JsliceMang.save()为何不能撤销掉啊？不是同一个Database问题。
     * 写写冲突：同一个数据库的，可能会被直接串行化而免去回滚异常，也有可能是异常回滚；跨两个数据库辅助数据库写写冲突可能抛异常但光光其它Database异常的主数据库可能不会回滚。
     * 一个用户连续两次点击按钮发送请求：导致多线程并发，事务针对DB，但对于java无法阻止并发逻辑错误！volatile也没法规避。
     * Transactional(rollbackFor=Exception.class, timeout=30)就能抛出JpaSystemException: transaction timeout expired;马后炮等到做完才说超时？
    * */
    @MetricsLogger
    @MutationMapping
    @Transactional(rollbackFor=Exception.class, timeout=250)
    public JsliceMang syncEqpFromLegacy(@Argument String type,@Argument String arg,@Argument String pasw,@Argument Integer offset) {
        User user=checkAuth();
        if(null==user)  throw new UserAccessDenyException("没登录");
        Boolean doModoffs= StringUtils.hasText(arg) && arg.startsWith("ModOffs:");    //复合多个按钮的操作接口函数模式
        JsliceMang ackjm=new JsliceMang();
        JsliceMang jobm;
        if("OD".equals(type)) {
            if(doModoffs)  arg=arg.substring(8);    //ModOffs:剔除掉
            jobm = jsliceMangRepository.findByName(type + arg);     //并发多线程模式作业:需要独立管理器
        }
        else
            jobm= jsliceMangRepository.findByName(type);
        Assert.isTrue(null!=jobm,"没job:"+type+" "+arg);
        if(doModoffs) {
            jobm.setOffs(Math.max(offset, 0));
            jobm.setFireor(user);
            jobm.setAuid(null);
            if(0==offset) {    //手动释放内存，不能立刻看见释放
                buffAKread = null;
                System.gc();
                jobm.setSortat(null);       //适配searchAfter模式:只能从头再来。
                //jsliceMangRepository.save(jobm);      //save不清楚是否新的Insert可能性：所以要争用索引：和其它人save(其它key)冲突!
                jsliceMangRepository.upsert(jobm);      //upsert()没效果；主要原因=findByName()没索引-导致“全表搜索”-引起SSI事务失败！
            }else{
                jsliceMangRepository.upsert(jobm);
                Assert.isTrue(!StringUtils.hasText(jobm.getSortat()),"searchAfter模式:不能自动设置偏移量,只能手动改表值");
                Assert.isTrue(offset<0,"并发不能用参数offs");
            }
            return jobm;
        }
        if("OD".equals(type))
            Assert.isTrue(offset<0,"并发不能用参数offs");
        Boolean innerOk=true, alljobFin=false;
        LocalDateTime now= LocalDateTime.now();
        //if(!StringUtils.hasText(type) || !StringUtils.hasText(pasw))  return jobm;
        if(null!=jobm.getFireor() && jobm.getFireor().getId().equals(user.getId())) {
            if ("请再三确认分片偏移量已经不再继续移动".equals(jobm.getAuid()) && "sure".equals(pasw)) {
                String uuid = UUID.randomUUID().toString().trim().replaceAll("-", "");
                jobm.setDesc(String.format("抢占任务终于可行了"));
                jobm.setLast(now);
                jobm.setAuid(uuid);
                jsliceMangRepository.save(jobm);
                return jobm;
            }
        }
        if(!pasw.equals(jobm.getAuid()) && !pasw.equals(jobm.getPasw()) ){
            jobm.setDesc("密码或auid不对");
            jobm.setAuid(null);
            return jobm;    //@自动保存的！
        }
        //【阻塞模式】支持一次性作业部分：(非分片的作业) ：调整graphql.ansyc默认30秒,超时丢弃不给前端应答。
        if("JU".equals(type)) {
            innerOk= setupFromLegacy_JU(arg);
            alljobFin=true;
        }else if("AU".equals(type)) {
            innerOk= syncEqpFromLegacy_AU(type, arg);
            alljobFin=true;
        }else if("Eu".equals(type)) {
            innerOk= syncEqpFromLegacy_Eu(type, arg);
            alljobFin=true;
        }
        else if("AD".equals(type)) {
            innerOk= syncUnitFromLeg_AD(type, arg);
            alljobFin=true;
        }else if("SY".equals(type)) {
            jobm.setDesc( setupFromLegacy_SY(arg) );
            alljobFin=true;
        }
        if(!alljobFin && jobm.getOffs()>=jobm.getTotl() )
            alljobFin=true;
        if(alljobFin){
            if(!StringUtils.hasText(jobm.getDesc()))      jobm.setDesc(innerOk? "全部都完成": "失败");
            jobm.setAuid(null);
            return jobm;
        }
        Duration duration;
        if(null==jobm.getLast())    duration= Duration.ofHours(1);
        else    duration= Duration.between(jobm.getLast(), now);
        if(!pasw.equals(jobm.getAuid()) && duration.toSeconds()<=60){
            //jobm.setAuid("正在抢占");   toMinutes<=1实际代表极限120秒
            jobm.setDesc(String.format("稍等%d秒:解锁中",61-duration.toSeconds()) );
            jobm.setAuid(null);
            jsliceMangRepository.save(jobm);
            return jobm;
        }
        String uuid=UUID.randomUUID().toString().trim().replaceAll("-", ""); //生成UUID重复几率非常小;
        //超过1分钟了，就解除锁定
        if(duration.toMinutes()>1){
            if(!pasw.equals(jobm.getAuid())) {          // auid != pasw;  pasw !='sure'
                if(offset>=0)  jobm.setOffs(offset);
                jobm.setDesc(String.format("超时自动解锁,%d秒", duration.toSeconds()));
                //jobm.setAuid("确认停止了");
                jobm.setFireor(user);
                //抢占任务特别注意！ 很可能前面的分片作业任务还没有真正完成啊[有重复可能]。 最好要冷静观察当前的进度变化情况。
                //加了@Transactional后 逻辑完全不同！
                jobm.setAuid("请再三确认分片偏移量已经不再继续移动");
                jsliceMangRepository.save(jobm);     //挪到jobm.setAuid("请再三；  后面
                return jobm;
            }
            if(!jobm.getFireor().getId().equals(user.getId()))
            {
                jobm.setDesc(String.format("任务被抢占了,太久%d秒", duration.toSeconds()));
                return jobm;
            }
            //Auid验证过了:允许实际时间超出1分钟，可持续占用,但有危险可能被抢占任务【并发执行机会】; 前端程序没有响应超时之后就能解锁。
            jobm.setAuid(uuid);
            jobm.setLast(now);
             //  jsliceMangRepository.save(jobm); 不能两次save导致version乐观锁冲突报错； version感觉没用到？
        }else{
            jobm.setAuid(uuid);
            //  jsliceMangRepository.save(jobm);  不能两次save导致version乐观锁冲突报错；
        }
        int enterOffs =jobm.getOffs();
        Boolean syncOK=true;
        JsliceMang retjm=new JsliceMang(jobm.getName(),jobm.getOffs(),jobm.getLimt(), LocalDateTime.now());      //非数据库的，不涉及事务=就不报错。
        //【切分单步任务】真分片作业部分：【待机掉电?导致中断】场合：部分保存DB后续网络失败fail='读实体失败1',offs应当后退一个limt才能确保数据完整。
        if("OD".equals(type))    syncOK=makeSomeForOD(jobm,arg);
        else if("Su".equals(type)){
            retjm=constructUnitSu(retjm,arg);        //?内部单独开启事务
            if(StringUtils.hasText(retjm.getDesc()))   syncOK=false;
            else  jobm.setOffs(retjm.getOffs());
        }
        else if("Jf".equals(type))    syncOK=setupFromLegacy_Jf(jobm,arg);
        else if("AK".equals(type))    syncOK=syncEqpFromLegacy_AK(jobm,arg);
        else if("VF".equals(type))    syncOK=buildVillageEtcVF(jobm,arg);
        else if("TZ".equals(type))    syncOK=makeSomeForTZ(jobm,arg);
        else if("SI".equals(type))    syncOK=makeSomeForSI(jobm,arg);
        else if("DT".equals(type))    syncOK=makeSomeForDT(jobm,arg);
        else Assert.isTrue(false,"未定义type:"+type);
        log.info("syncEqpFromOld:Finish，type={}",type);
        LocalDateTime now2= LocalDateTime.now();
        //没有事务Transactional约束：不同客户看到不同的jobm,jobm实际由其他线程已经修改了还不知道啊。同一个客户findByName2俩次看到一样的对象。
        jobm.setLast(now2);
        if(enterOffs>=jobm.getOffs()){        //有可能Offs比预定的jobm.totl()跑得更远。
            jobm.setDesc("无法前进offset");     //实际已经完成{分片偏移后无法找到更多条的数据记录}
            jobm.setAuid(null);
        }
        if(!syncOK)  jobm.setAuid(null);    //终止执行 报错暂停; CRDB经常都会报错，都会暂停!!
        //被constructUnitSu函数修改: 报错StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) :.JsliceMang
        jsliceMangRepository.save(jobm);
        //optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException:
        ackjm.setOffs(jobm.getOffs());
        ackjm.setAuid(jobm.getAuid());
        ackjm.setFireor(jobm.getFireor());
        ackjm.setDesc(jobm.getDesc());
        return ackjm;     //此时的jobm内容没有保存数据库的，只是给前端看的。
    }
    //维护　建地级市表
    public Boolean syncUnitFromLeg_AD建地级市表() {
        Iterable<Province>  pall= provinceRepository.findAll();
        AtomicInteger sum= new AtomicInteger();
        for (Province province:pall) {
            QDictArea qm = QDictArea.dictArea;
            BooleanBuilder builder = new BooleanBuilder();
            builder.and(qm.FAU_TYPE_PARENT_CODE.eq(province.getOldId()));
            Iterable<DictArea> list = dictAreaRepository.findAll(builder);
            list.forEach(item -> {
                City mone = new City();
                mone.setProvince(province);
                mone.setName(item.getFAU_TYPE_NAME());
                mone.setOldId(item.getId());
                cityRepository.save(mone);
                sum.getAndIncrement();
                log.info("地级市:{}{} id={}", province.getName(),item.getFAU_TYPE_NAME(), mone.getId());
            });
        }
        log.info("AD建地级市:Fin 合计{}",sum);
        return true;
    }
    //维护 建省份表
    public Boolean syncUnitFromLeg_AD建省份表() {
        Country country=countryRepository.findByName("中国").orElse(null);
        Assert.isTrue(null!=country,"奇");
        QDictArea qm = QDictArea.dictArea;
        BooleanBuilder builder = new BooleanBuilder();
        builder.and(qm.FAU_TYPE_PARENT_CODE.eq(1L));  //1=countryid 中国
        AtomicInteger sum= new AtomicInteger();
        Iterable<DictArea> list = dictAreaRepository.findAll(builder);
        list.forEach(item -> {
            Province province=new Province();
            province.setCountry(country);
            province.setName(item.getFAU_TYPE_NAME());
            province.setOldId(item.getId());
            provinceRepository.save(province);
            sum.getAndIncrement();
            log.info("shenfen:{} id={}", item.getFAU_TYPE_NAME(), province.getId());
        });
        log.info("sync建省份:Fin {}", sum);
        return true;
    }
    /**【人工改个半死】建区县表;  【人工修正】旧平台抄来的区划代码基础表中有重复数据=报错！
     * 内蒙古自治区呼伦贝尔市新巴尔虎- 重名 人工改成：150727右旗  150726左旗; 内蒙古自治区乌兰察布盟察哈尔右-237新疆维吾尔自治区巴音郭楞蒙古自-231
     * 新疆维吾尔自治区昌吉回族自治州-229新疆维吾尔自治区博尔塔拉蒙古自-230新疆维吾尔自治区伊犁哈萨克自治-218新疆维吾尔自治区克孜勒苏柯尔克-215
     select * from fjtj.public.tb_dict_area where fau_type_name in(select fau_type_name from(
       select fau_type_parent_code,fau_type_name,count(*) from fjtj.public.tb_dict_area group by fau_type_parent_code,fau_type_name having count(*)>1 ))
        order by fau_type_name
     查询并修改确保重复清零；
     * */
    public Boolean syncUnitFromLeg_AD建区县表() {
        AtomicInteger sum= new AtomicInteger();
        Iterable<City>  pall= cityRepository.findAll();
        for (City parent:pall) {
            QDictArea qm = QDictArea.dictArea;
            BooleanBuilder builder = new BooleanBuilder();
            builder.and(qm.FAU_TYPE_PARENT_CODE.eq(parent.getOldId()));
            Iterable<DictArea> list = dictAreaRepository.findAll(builder);
            list.forEach(item -> {
                County mone = new County();
                mone.setCity(parent);
                mone.setName(item.getFAU_TYPE_NAME());
                mone.setOldId(item.getId());
                try {
                    countyRepository.save(mone);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                sum.getAndIncrement();
                log.info("区县:{}>{} id={}", parent.getName(),item.getFAU_TYPE_NAME(), mone.getId());
            });
        }
        log.info("建区县表:Fin sum={}", sum);
        return true;
    }
    //[目前只有福建省的]街道乡镇表
    public Boolean syncUnitFromLeg_AD街道乡镇(String type) {
        AtomicInteger sum= new AtomicInteger();
        Iterable<County>  pall= countyRepository.findAll();
        for (County parent:pall) {
            QDictArea qm = QDictArea.dictArea;
            BooleanBuilder builder = new BooleanBuilder();
            builder.and(qm.FAU_TYPE_PARENT_CODE.eq(parent.getOldId()));
            Iterable<DictArea> list = dictAreaRepository.findAll(builder);
            list.forEach(item -> {
                Town mone = new Town();
                mone.setCounty(parent);
                mone.setName(item.getFAU_TYPE_NAME());
                mone.setOldId(item.getId());
                try {
                    townRepository.save(mone);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                sum.getAndIncrement();
                log.info("街镇:{}>{} id={}", parent.getName(),item.getFAU_TYPE_NAME(), mone.getId());
            });
        }
        log.info("街道乡镇表: Fin={}", sum);
        return true;
    }
    //从Town自动构造Adminunit, 行政管理单元：高级别的行政管理单元？
    public Boolean syncUnitFromLeg_AD管理单元(String type) {
        AtomicInteger sum= new AtomicInteger();
        Iterable<Town>  pall= townRepository.findAll();
        for (Town parent:pall) {
            Adminunit adminunit=adminunitRepository.findByTownIs(parent); //不允许嵌套关系的Adminunit存在，不应该有大小级别区域共存。
            if(null==adminunit) {       //旧的，可重新执行模式，分片+已经初始化
                adminunit = new Adminunit();
                adminunit.setTown(parent);
            }
            DictArea dictArea= dictAreaRepository.findById(parent.getOldId()).orElse(null);  //不同数据库的
            adminunit.setAreacode(dictArea.getFAU_TYPE_CODE());
            adminunit.setPrefix("中国"+parent.getName());  //给快递员看的
            adminunit.setCounty(parent.getCounty());
            adminunit.setCity(parent.getCounty().getCity());
            adminunit.setProvince(parent.getCounty().getCity().getProvince());
            adminunit.setCountry(parent.getCounty().getCity().getProvince().getCountry());
            //没有设置 zipcode ?
            adminunitRepository.save(adminunit);    //不能用saveAndFlush(),执行很慢的！！
            sum.getAndIncrement();
            log.info("行政单元{}:{}; 区划码={}", sum,parent.getName(), dictArea.getFAU_TYPE_CODE());
        }
        log.info("构建管理单元:Fin sum={}", sum);
        return true;
    }
    /**维护　楼盘小区;【錯誤停止】 Duplicate entry失败大約8个手动改名！
     * Duplicate entry '永安市五洲第一城Ⅲ期-432' for key   永安市五洲第一城III期
     * 人工把TB_HOUSE_MGE 永安市五洲第一城Ⅲ期 改成 永安市五洲第一城Ⅲ期2; 可重新执行。報錯樓盤名字後面+2后保存重新跑。
     * */
    public <T> String genrVlgDivisionVF导楼盘小区vL(T inObj) {
           //城Ⅲ期 对 城III期竟然冲突，数据库查询时刻当作不同，可是唯一性索引就会报错却当成重复！。
        String mesg="";
        HouseMge parent= (HouseMge)inObj;
        if("删除".equals(parent.getBUILD_STATE()))    return mesg;
        QVillage qm = QVillage.village;
        BooleanBuilder builder = new BooleanBuilder();
        builder.and(qm.oldId.eq(parent.getId()));       //可重做模式，不用清空旧的Village表。
        Village village=villageRepository.findOne(builder).orElse(null);
        if(null==village)   village=new Village();
        village.setName(parent.getBUILD_NAME());
        village.setType(parent.getINST_BUILD_TYPE());
        village.setOldId(parent.getId());
        village.setAddress(parent.getBUILD_ADDR());
        Adminunit adminunit=adminunitRepository.findTopByAreacode(parent.getAREA_COD());
        if(null!=adminunit) {
            BooleanBuilder builder2 = new BooleanBuilder();
            builder2.and(qm.ad.eq(adminunit));
            builder2.and(qm.name.eq(parent.getBUILD_NAME()));
            builder2.and(qm.oldId.ne(parent.getId()));      //不是当前这个
            Village village2=villageRepository.findOne(builder2).orElse(null);
            if(null!=village2){   //BUILD_NAME多个空格能分辨清楚；反而是其他标点符号全角半角无法区分造成重复。奇怪IDEA console查询可当成不同的.
                mesg="该Admin下重复楼盘name";
            }else
                village.setAd(adminunit);
        }
        //adminunit==null 允许生成，待后续人工补充。
        if(!"".equals(mesg))   return mesg;
        else{
            villageRepository.save(village);
            log.info("楼盘:{} AdPr={}",parent.getBUILD_NAME(), null!=adminunit? adminunit.getPrefix() : parent.getBUILD_ADDR());
        }
        return mesg;
    }
    /**导来区划整个表
     * */
    public Boolean syncUnitFromLeg_AD第一步(String type) {
        if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
        Assert.isTrue(emFjtj.isJoinedToTransaction(),"没emFjtj Transaction");
        ReadComm<OdlqArea> result= Utilities.getItArrType(OdlqArea.class,14,"1=1");
        int countall=result.getArr().size();
        log.info("syncEqpFromOld:type={},条数{},服务应答{}",type, countall,result.getErrorCode());
        int lopct=0;
        List<DictArea> batch=new ArrayList<>();
        for (Object one1: result.getArr())
        {
            OdlqArea one= (OdlqArea)one1;
            lopct++;
            String json = JSON.toJSONString(one);
            DictArea obj=JSON.parseObject(json, DictArea.class);
            batch.add(obj);
            if(lopct>=800) {
                dictAreaRepository.saveAll(batch);
                lopct=0;
                batch.clear();
            }
        }
        dictAreaRepository.saveAll(batch);
        log.info("syncEqpFromOld:完成，type={},条数{}",type, countall);
        return true;
    }
    /**国家表：只有名称*/
    public Boolean syncUnitFromLeg_AD第二步(String type) {
        QDictArea qm = QDictArea.dictArea;
        BooleanBuilder builder = new BooleanBuilder();
        builder.and(qm.FAU_TYPE_PARENT_CODE.eq(391L));      //国家代码
        ArrayList<DictArea> list =(ArrayList)dictAreaRepository.findAll(builder);
        AtomicInteger countall= new AtomicInteger();
        log.info("找国家:type={},条数{}",type, list.size());
        list.forEach(item -> {
            Country country=new Country();
            if(item.getFAU_TYPE_NAME().startsWith("国外"))
                country.setName(item.getFAU_TYPE_NAME().substring("国外".length()));
            else country.setName(item.getFAU_TYPE_NAME());
            countryRepository.save(country);
            countall.getAndIncrement();  //点击IDEA主动修改代码，不能用int countall=0; countall++;
        });
        Country country=new Country();
        country.setName("中国");
        countryRepository.save(country);
        log.info("syncUnitFromLeg_AD第二步:Fin {}", countall);
        //Assert.isTrue(list.size()==countall.get(),"多稀奇");
        return true;
    }
    public Boolean syncUnitFromLeg_AD(String type,String arg) {
        if("Al".equals(arg))    return syncUnitFromLeg_AD第一步(type);
        else if("cn".equals(arg))    return syncUnitFromLeg_AD第二步(type);
        else if("Pv".equals(arg))    return syncUnitFromLeg_AD建省份表();
        else if("Ct".equals(arg))    return syncUnitFromLeg_AD建地级市表();
        else if("qX".equals(arg))    return syncUnitFromLeg_AD建区县表();
        else if("TW".equals(arg))    return syncUnitFromLeg_AD街道乡镇(type);
        else if("Ex".equals(arg))    return syncUnitFromLeg_AD例外处理(type);
        else if("dY".equals(arg))    return syncUnitFromLeg_AD管理单元(type);
        else if("GJ".equals(arg))    return syncUnitFromLeg_AD管理单元GJ(type);
        else if("Lb".equals(arg))    return syncUnitFromLeg_AD导楼盘小区();
        return true;
    }
    /**刚生成的乡镇Town表，有些数据古怪，要修正下。 实际更多比如：
     * 福建省漳州市东山县康美镇及各村、百亿新城  ；福建省漳州市漳浦县长桥镇及所属范围 ； 福建省漳州市龙文区蓝田镇及各村、蓝田开发区
     * */
    public Boolean syncUnitFromLeg_AD例外处理(String type) {
        AtomicInteger sum= new AtomicInteger();
        Iterable<Town>  pall= townRepository.findAll();
        for (Town parent:pall) {
            String name=parent.getName();
            if(name.endsWith("及各村")) {
                parent.setName(name.substring(0, name.length()-3));
                townRepository.save(parent);
                sum.getAndIncrement();
                log.info("街镇:{}->{} id={}", name, parent.getName(), parent.getId());
            }
        }
        log.info("乡镇表有些古怪，要修正: Fin={}", sum);   //110条数据：漳州的*
        return true;
    }
    /**导楼盘小区导进到虚fjtj数据库*/
    public Boolean syncUnitFromLeg_AD导楼盘小区() {
        if(!emFjtj.isJoinedToTransaction())      emFjtj.joinTransaction();
        Assert.isTrue(emFjtj.isJoinedToTransaction(),"没emFjtj Transaction");
        ReadComm<Housebd> result= Utilities.getItArrType(Housebd.class,15,"1=1");
        int countall=result.getArr().size();
        log.info("导楼盘小区:条数{},服务应答{}", countall,result.getErrorCode());
        int lopct=0;
        List<HouseMge> batch=new ArrayList<>();
        for (Object one1: result.getArr())
        {
            Housebd one= (Housebd)one1;
            lopct++;
            String json = JSON.toJSONString(one);
            HouseMge obj=JSON.parseObject(json, HouseMge.class);
            obj.setId(one.getBUILD_ID());
            batch.add(obj);
            if(lopct>=800) {
                houseMgeRepository.saveAll(batch);
                lopct=0;
                batch.clear();
            }
        }
        houseMgeRepository.saveAll(batch);
        log.info("导楼盘小区-fjtj:Fin 条数{}", countall);
        return true;
    }
    //泛型，由外部调用者来决定<T>最终类型。 去掉<T> 改成 implements interface SliceSyncRes模式;
    public String buildVillageEtc单个VF(SliceSyncRes parentObj,String arg) {
        String retRes = "";
        if("vL".equals(arg))    retRes=genrVlgDivisionVF导楼盘小区vL(parentObj);
        else if("jS".equals(arg))    retRes=genrVlgDivisionVF分支机构jS(parentObj);
        else if("jD".equals(arg))    retRes=genrVlgDivisionVF安全部门jD(parentObj);
        else if("cS".equals(arg))    retRes=genrVlgDivisionVF分支机构cS(parentObj);
        else if("cD".equals(arg))    retRes=genrVlgDivisionVF安全部门cD(parentObj);
        if(StringUtils.hasText(retRes))  parentObj.setFail(retRes);
        return retRes;
    }
    //检验机构.indCod("特种设备检验机构")必须是公司！不能是个人。
    public Boolean syncFromLegacy_Eu检验机构IS(String arg) {
        if(!emIncp.isJoinedToTransaction())      emIncp.joinTransaction();
        ReadComm<Jcunit> result= Utilities.getItArrType(Jcunit.class,214,"1=1");
        int countall=result.getArr().size();
        //终端交互才会用System.out.println((new String()).format("例行Utilities.average运行=%d",result.getErrorCode()) );
        log.info("检验机构: 条数{},服务应答{}", countall,result.getErrorCode());
        int nowcnt=0;
        int lopct=0;
        List<JispUnit> batch=new ArrayList<>();
        for (Object one1: result.getArr())
        {
            Jcunit one= (Jcunit)one1;
            String json = JSON.toJSONString(one);       //支持自循环嵌套不报错
            JispUnit obj=JSON.parseObject(json, JispUnit.class);
            //找到Unit或生成新的：合并到统一单位表，不要拆开成多个单独表！
            QCompany qm = QCompany.company;
            BooleanBuilder builder = new BooleanBuilder();
            builder.and(qm.name.eq(one.getUntName()));
            //builder.and(qm.no.eq(one.getUntOrgCod()));
            Unit unit;      //检验机构 必然 是公司的！公司的名字全国内必须唯一性。No,address?采信那个数据？@监察单位也很烂？？
            Company company= companies.findOne(builder).orElse(null);   //不能返回多个=报错！
            if(null!=company){
                unit= units.findUnitByCompany(company);
                if(null==unit)  return false;
            }else{
                company=Company.builder().name(one.getUntName()).no(one.getUntOrgCod())
                        .build();
                unit=Unit.builder().build();
            }
            Adminunit adminunit=adminunitRepository.findTopByAreacode(one.getUntAreaCod());
            company= company.toBuilder().phone(one.getUntPhone()).address(one.getUntAddr()).linkMen(one.getUntLkmen())
                    .ad(adminunit).build();
            //这一步 ？？不像会把先前unit:oldId(旧平台检验fjtj.tb_unt_mge单位ID),给覆盖没掉了？ 奇怪也不是啊；是no号码不一样，又重新生成一个unit的！
            unit= unit.toBuilder().company(company).area(one.getUntAreaCod())
                    .build();
            try {
                companies.save(company);
                units.save(unit);
            } catch (Exception e) {
                e.printStackTrace();
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return false;
            }
            CompanyEs companyEs=new CompanyEs();
            BeanUtils.copyProperties(company,companyEs);
            companyEsRepository.save(companyEs);
            //准备Unit结束
            obj.setUunit(unit.getId());    //两个表一起生成数据。
            lopct++;
            nowcnt++;
            batch.add(obj);
            log.info("检验机构:{}-> {}",nowcnt, obj.getUntName());
            if(lopct>=200) {
                jispUnitRepository.saveAll(batch);
                lopct=0;
                batch.clear();
            }
        }
        jispUnitRepository.saveAll(batch);
        log.info("检验机构:完成， 条数{}", countall);
        return true;
    }
    public Boolean setupFromLegacy_JU检验分支(String arg) {
        if(!emFjtj.isJoinedToTransaction())     emFjtj.joinTransaction();
        Assert.isTrue(emFjtj.isJoinedToTransaction(),"没emFjtj Transaction");
        ReadComm<Usecudp> result= Utilities.getItArrType(Usecudp.class,16,"1=1");
        int countall=result.getArr().size();
        log.info("检验分支: 条数{},服务应答{}", countall,result.getErrorCode());
        int nowcnt=0;
        int lopct=0;
        List<UntSecudept> batch=new ArrayList<>();
        for (Object one1: result.getArr())
        {
            Usecudp one= (Usecudp)one1;
            String json = JSON.toJSONString(one);
            UntSecudept obj=JSON.parseObject(json, UntSecudept.class);   //实际类似new UntSecudept()没差别;
            lopct++;
            nowcnt++;
            batch.add(obj);
            log.info("检验机构:{}-> {}",nowcnt, obj.getName());
            if(lopct>=500) {
                untSecudeptRepository.saveAll(batch);
                lopct=0;
                batch.clear();
            }
        }
        untSecudeptRepository.saveAll(batch);   //可自动依靠主键覆盖掉旧数据！
        log.info("检验分支:完成， 条数{}", countall);
        return true;
    }
    public Boolean setupFromLegacy_JU检验部门(String arg) {
        if(!emFjtj.isJoinedToTransaction())     emFjtj.joinTransaction();
        Assert.isTrue(emFjtj.isJoinedToTransaction(),"没emFjtj Transaction");
        ReadComm<OldDept> result= Utilities.getItArrType(OldDept.class,17,"1=1");
        int countall=result.getArr().size();
        log.info("检验部门: 条数{},服务应答{}", countall,result.getErrorCode());
        int nowcnt=0;
        int lopct=0;
        List<UntDept> batch=new ArrayList<>();
        for (Object one1: result.getArr())
        {
            OldDept one= (OldDept)one1;
            String json = JSON.toJSONString(one);
            UntDept obj=JSON.parseObject(json, UntDept.class);
            lopct++;
            nowcnt++;
            batch.add(obj);
            log.info("检验部门:{}-> {}",nowcnt, obj.getName());
            if(lopct>=500) {
                untDeptRepository.saveAll(batch);
                lopct=0;
                batch.clear();
            }
        }
        untDeptRepository.saveAll(batch);
        log.info("检验部门:完成， 条数{}", countall);
        return true;
    }
    public Boolean setupFromLegacy_JU监察分支(String arg) {
        if(!emIncp.isJoinedToTransaction())     emIncp.joinTransaction();
        Assert.isTrue(emIncp.isJoinedToTransaction(),"没emIncp Transaction");
        ReadComm<Usecudp> result= Utilities.getItArrType(Usecudp.class,216,"1=1");
        int countall=result.getArr().size();
        log.info("监察分支: 条数{},服务应答{}", countall,result.getErrorCode());
        int nowcnt=0;
        int lopct=0;
        List<JuntSecudept> batch=new ArrayList<>();
        for (Object one1: result.getArr())
        {
            Usecudp one= (Usecudp)one1;
            String json = JSON.toJSONString(one);
            JuntSecudept obj=JSON.parseObject(json, JuntSecudept.class);
            lopct++;
            nowcnt++;
            batch.add(obj);
            log.info("监察机构:{}-> {}",nowcnt, obj.getName());
            if(lopct>=500) {
                juntSecudeptRepository.saveAll(batch);
                lopct=0;
                batch.clear();
            }
        }
        juntSecudeptRepository.saveAll(batch);   //被删除的旧数据必须自己去清理。旧id会被保留。所以要事先清空全表！！
        log.info("监察分支:完成， 条数{}", countall);
        return true;
    }
    public Boolean stepJob_JU监察部门(int offs,String cond) {
        if(!emIncp.isJoinedToTransaction())     emIncp.joinTransaction();
        Assert.isTrue(emIncp.isJoinedToTransaction(),"没emIncp Transaction");
        log.info("stepJob_JU监察部门:offs={} cond:{}", offs, cond);
        ReadComm<OldDept> result= Utilities.getItArrType(OldDept.class,217,cond);
        int countall=result.getArr().size();
        log.info("监察部门: 条数{},服务应答{}", countall,result.getErrorCode());
        int nowcnt=0;
        int lopct=0;
        List<JcuntDept> batch=new ArrayList<>();
        for (Object one1: result.getArr())
        {
            OldDept one= (OldDept)one1;
            String json = JSON.toJSONString(one);
            JcuntDept obj=JSON.parseObject(json, JcuntDept.class);
            lopct++;
            nowcnt++;
            batch.add(obj);
            if(lopct>=500) {
                log.info("监察部门:{}-> {}",nowcnt, obj.getName());
                jcuntDeptRepository.saveAll(batch);
                lopct=0;
                batch.clear();
            }
        }
        jcuntDeptRepository.saveAll(batch);
        log.info("监察部门:完成，cond={} 条数{}准备提交",cond,countall);
        return true;
    }
    public Boolean syncFromLegacy_AU永久Pm(String arg) {
        if(!emIncp.isJoinedToTransaction())     emIncp.joinTransaction();
        ReadComm<PermtUnt> result= Utilities.getItArrType(PermtUnt.class,218,"1=1");
        int countall=result.getArr().size();
        log.info("永久单位: 条数{},服务应答{}", countall,result.getErrorCode());
        int nowcnt=0;
        int lopct=0;
        List<JcPermtUnt> batch=new ArrayList<>();
        for (Object one1: result.getArr())
        {
            PermtUnt one= (PermtUnt)one1;
            String json = JSON.toJSONString(one);
            JcPermtUnt obj=JSON.parseObject(json, JcPermtUnt.class);
            lopct++;
            nowcnt++;
            batch.add(obj);
            log.info("永久单位:{}-> {}",nowcnt, obj.getName());
            if(lopct>=500) {
                jcPermtUntRepository.saveAll(batch);
                lopct=0;
                batch.clear();
            }
        }
        jcPermtUntRepository.saveAll(batch);
        log.info("永久单位:完成， 条数{}", countall);    //这里完成不代表立刻应答前端，随后数据库提交时间消耗更长时间：随后事务时间在磁盘看出性能瓶颈。
        return true;
    }
    //监察永久单位表 簡單映射到新平台Unit; 传递<T>=SliceSyncRes=同步作业目标实体单条记录。
    public <T> Triple constructUnit_Su监察批Pm(T inObj) {
        String mesg="";
        JcPermtUnt iden= (JcPermtUnt)inObj;
        if( (null!=iden.getIS_DEL() && iden.getIS_DEL()) || !StringUtils.hasText(iden.getName()))
            return new Triple("无效", null, null);
        //這個類型單位，多數是企業
        boolean isPerson=iden.getName().length()<=3 ||
                     (iden.getORG_COD().length()==18 && iden.getORG_COD().startsWith("35") );
        if(!isPerson){
            Company company= companies.findByName(iden.getName());
            if(null!=company){    //不一定必须在监察的3个单位表里面找到；若仅仅在检验单位表有的是也能匹配的;
                iden.setUunit(units.findUnitByCompany(company).getId());
                return new Triple(mesg, null, null);
            }
        }else{
            Person person= persons.findByNo(iden.getORG_COD());
            if(null!=person){
                iden.setUunit(units.findUnitByPerson(person).getId());
                return new Triple(mesg, null, null);
            }
        }
        //历史原因！输入起点，用户可随意输入企业名字，名字差错多。 湖北长江石化设备有限公司:特别！监察3表没找到，可检验有啊，所以PMT永久表映射成功。
        return new Triple("找不到", null, null);    //考虑新new Unit()? 单位表实际是外部数据源，并非本平台自己家维护！
    }
    /**注意：依赖关系；首先单位必须重整好了，才能更新部门分支表*/
    public <T> String  genrVlgDivisionVF分支机构jS(T inObj) {
        UntSecudept iden= (UntSecudept)inObj;
        if(!StringUtils.hasText(iden.getName()) || null==iden.getUNT_ID())    return "空";
        Unit unit= getUnitfromOldUnitId(false,iden.getUNT_ID());
        if(null==unit)  return "uniNil";
        Adminunit adminunit=adminunitRepository.findTopByAreacode(iden.getSECUDEPT_AREA_COD());
        Division division= divisionRepository.findByUnitAndName(unit, iden.getName());
        if(null==division)  division=new Division();
        division= division.toBuilder().name(iden.getName()).unit(unit).area(iden.getSECUDEPT_AREA_COD()).address(iden.getSECUDEPT_ADDR())
                .linkMen(iden.getLKMEN()).phone(iden.getMOBILE()).tel(iden.getPHONE()).ad(adminunit)
                .branch(true).frot("闽yS").build();
        divisionRepository.save(division);
        return "";
    }
    public <T> String  genrVlgDivisionVF安全部门jD(T inObj) {
        UntDept iden= (UntDept)inObj;
        if(!StringUtils.hasText(iden.getName()) || null==iden.getUntId())    return "空";
        Unit unit= getUnitfromOldUnitId(false,iden.getUntId());
        if(null==unit)  return "uniNil";
        Adminunit adminunit=adminunitRepository.findTopByAreacode(iden.getDEPT_AREA_COD());
        Division division= divisionRepository.findByUnitAndName(unit, iden.getName());
        if(null==division)  division=new Division();
        division= division.toBuilder().name(iden.getName()).unit(unit).area(iden.getDEPT_AREA_COD()).address(iden.getDEPT_ADDR())
                .linkMen(iden.getLKMEN()).phone(iden.getMOBILE()).tel(iden.getPHONE()).ad(adminunit)
                .branch(false).frot("闽yD").build();
        divisionRepository.save(division);
        return "";
    }
    //为何？监察分支机构并没有在检验对应分支机构重名出现？没有重叠:检验的使用单位 安装区域 分支机构 全部比监察的更新和可信任点。
    public <T> String  genrVlgDivisionVF分支机构cS(T inObj) {
        JuntSecudept iden= (JuntSecudept)inObj;
        if(!StringUtils.hasText(iden.getName()) || null==iden.getUNT_ID())    return "空";
        Unit unit= getUnitfromOldUnitId(true,iden.getUNT_ID(),false);    //福建监察平台对应id：从普通单位表查
        if(null==unit)  return "uniNil";
        Division division= divisionRepository.findByUnitAndName(unit, iden.getName());
        if(null==division)  division=new Division();
        Adminunit adminunit=adminunitRepository.findTopByAreacode(iden.getSECUDEPT_AREA_COD());
        division= division.toBuilder().name(iden.getName()).unit(unit).area(iden.getSECUDEPT_AREA_COD()).address(iden.getSECUDEPT_ADDR())
                .linkMen(iden.getLKMEN()).phone(iden.getMOBILE()).tel(iden.getPHONE()).ad(adminunit)
                .branch(true).frot("闽cS").build();
        divisionRepository.save(division);    //后面运行的任务就会覆盖前面已经执行的任务分片的保存实体。先生成检验后生成监察的可能覆盖掉。
        return "";
    }
    //监察数据：把关很不严谨，尽量舍弃！，可免得覆盖前面运行的检验平台同步步骤的已经生成部门(不管任务的前后运行顺序)。
    public <T> String  genrVlgDivisionVF安全部门cD(T inObj) {
        JcuntDept iden= (JcuntDept)inObj;
        if(!StringUtils.hasText(iden.getName()) || null==iden.getUntId())    return "空";
        //过滤掉 垃圾数据!
        if(iden.getUntId()==266785)    return "弃";  //南平已解体单位或不存在设备集
        if("13599000001".equals(iden.getMOBILE()))   return "虚";
        if( !StringUtils.hasText(iden.getLKMEN()) &&
            (null==iden.getMOBILE() || iden.getMOBILE().length()<8) &&
                (null==iden.getPHONE() || iden.getPHONE().length()<7)
        )   return "无联";
        Unit unit= getUnitfromOldUnitId(true,iden.getUntId(),false);   //福建监察平台对应id：从普通单位表查
        if(null==unit)  return "uniNil";
        //[注意,性能考虑] 给这个表 untId增加索引;     该单位底下：区区单独一个部门+可能另外还有分支机构+旧检验平台的数据还可能多出。
        if(jcuntDeptRepository.countAllByUntId(iden.getUntId())<=1)
            return "独部";         //1部门？ 拉进来意义不大。
        Division division= divisionRepository.findByUnitAndName(unit, iden.getName());
        if(null==division)  division=new Division();
        else    return "已有不采信";    //数据可能最烂，不采信；
        Adminunit adminunit=adminunitRepository.findTopByAreacode(iden.getDEPT_AREA());
        division= division.toBuilder().name(iden.getName()).unit(unit).area(iden.getDEPT_AREA()).address(iden.getDEPT_ADDR())
                .linkMen(iden.getLKMEN()).phone(iden.getMOBILE()).tel(iden.getPHONE()).ad(adminunit)
                .branch(false).frot("闽cD").build();
        divisionRepository.save(division);
        return "";
    }
    /**除了Town/Country级别; 给高级别的(省/市/县)构造Adminunit行政管理单元
     * */
    public Boolean syncUnitFromLeg_AD管理单元GJ(String type) {
        int sum=0;
        Iterable  pall= provinceRepository.findAll();
        for (Province parent: (Iterable<Province>)pall) {
            Adminunit adminunit=adminunitRepository.readAdminunitByProvinceAndAndCity(parent,null);  //最多返回一个
            if(null==adminunit) {
                adminunit = new Adminunit();
                adminunit.setProvince(parent);
            }
            DictArea dictArea= dictAreaRepository.findById(parent.getOldId()).orElse(null);
            adminunit.setAreacode(dictArea.getFAU_TYPE_CODE());
            adminunit.setPrefix("中国"+parent.getName());  //给快递员看的
            adminunit.setCountry(parent.getCountry());
            //没有设置 zipcode ?
            adminunitRepository.save(adminunit);
            sum++;
            log.info("省级行政单元{}:{}; 区划码={}", sum,parent.getName(), dictArea.getFAU_TYPE_CODE());
        }
        sum=0;
        pall= cityRepository.findAll();
        for (City parent: (Iterable<City>)pall) {
            Adminunit adminunit=adminunitRepository.readAdminunitByCityAndAndCounty(parent,null);  //最多返回一个
            if(null==adminunit) {
                adminunit = new Adminunit();
                adminunit.setCity(parent);
            }
            DictArea dictArea= dictAreaRepository.findById(parent.getOldId()).orElse(null);
            adminunit.setAreacode(dictArea.getFAU_TYPE_CODE());
            adminunit.setPrefix("中国"+parent.getName());  //给快递员看的
            adminunit.setProvince(parent.getProvince());
            adminunit.setCountry(parent.getProvince().getCountry());
            //没有设置 zipcode ?
            adminunitRepository.save(adminunit);
            sum++;
            log.info("地市行政单元{}:{}; 区划码={}", sum,parent.getName(), dictArea.getFAU_TYPE_CODE());
        }
        sum=0;
        pall= countyRepository.findAll();
        for (County parent: (Iterable<County>)pall) {
            Adminunit adminunit=adminunitRepository.readAdminunitByCountyAndTown(parent,null);  //最多返回一个
            if(null==adminunit) {
                adminunit = new Adminunit();
                adminunit.setCounty(parent);
            }
            DictArea dictArea= dictAreaRepository.findById(parent.getOldId()).orElse(null);
            adminunit.setAreacode(dictArea.getFAU_TYPE_CODE());
            adminunit.setPrefix("中国"+parent.getName());
            adminunit.setCity(parent.getCity());
            adminunit.setProvince(parent.getCity().getProvince());
            adminunit.setCountry(parent.getCity().getProvince().getCountry());
            //没有设置 zipcode ?
            adminunitRepository.save(adminunit);
            sum++;
            log.info("县区级行政单元{}:{}; 区划码={}", sum,parent.getName(), dictArea.getFAU_TYPE_CODE());
        }
        log.info("构建GJ管理单元:Fin sum={}", sum);
        return true;
    }
    //分片作业，设备台账 更新ES等；
    public Boolean makeSomeForTZ(JsliceMang jobm,String arg) {
        Function<StoreSync, Triple<?,?,?>>  sliceJob= parb ->{
            if("Kg".equals(arg))    return makeFor单TZ设备过滤Kg(parb);
            else return null;
        };
        if("Kg".equals(arg)){
            return jobm.runBatchSave(storeSyncRepository, sliceJob, null, null);
        }
        else return false;   //泛型决定parb最终类型。 sliceJob是嵌入在Loop内部的函数块。
    }
    /**OD作业并发 #totl设置OD.1=175000 /350000 /525000 /700000 而OD.5=910000; offs设置=上一个线程totl;
     * 并发多线程方式：arg=“.1”等； offset=N开始代表前面省略掉N条的记录。展示层显示第N+1条正式启动；totl标识最后一次Loop,实际作业可允许超过total对应的那一条记录。
    * */
    public Boolean makeSomeForOD(JsliceMang jobm,String arg) {
        Function<SliceSyncRes, Triple<?,?,?>>  sliceJob= parb ->{
            if(".1".equals(arg) || ".2".equals(arg) || ".3".equals(arg) || ".4".equals(arg) || ".5".equals(arg) )
                return syncEqp单设备OD并发(parb, arg);
            else return null;
        };
        //后面StoreSync.class是实体投影；
        return jobm.runBatchSlice(storeSyncRepository, sliceJob, null, null,StoreSync.class);
    }
    //测试：
    public <T> Triple  makeFor单个SI维护bC(List<EqpEs> packs) {
        EqpEs inObj= packs.get(0);
        String retRes="";
        UUID eqpid= inObj.getId();    //？？泛型==欺骗编译器;
        log.info("排序asc作业EqpEs {},{} {}:{}", inObj.getUseu().getId(), inObj.getNxtd2(),inObj.getNxtd1(), inObj.getCod());
        //事务当中查询出来的数据库实体：是可以自动保存的,new()不算。
        return new Triple(retRes, null, null);
    }
    /**单个包的设备看需要生成任务吗: 使用单位和(下次)日期一样的。
     * 参数jyzq: 检验类型： 0=全面定期检验， 1=年度检验，在线检验的业务；
     * */
    public <T> Triple  makeFor单个SI任务生成(List<EqpEs> packs,int jyzq) {
        String retRes="";
        EqpEs base=packs.get(0);      //包的 单位和任务日期getNxtd2/1()不变
        LocalDate today= LocalDate.now();
        BusinessCat_Enum  baseBusType= BusinessCat_Enum.REGUL;
        LocalDate  basejyDate= base.getNxtd2();
        if(1==jyzq) {
            baseBusType= BusinessCat_Enum.ANNUAL;
            basejyDate= base.getNxtd1();
        }
        QTask qm = QTask.task;
        Sort sort=Sort.by(Sort.Order.asc("date"));       //【不可省略】顺序排列
        BooleanBuilder builder = new BooleanBuilder();
        BooleanBuilder statusbuild = new BooleanBuilder();
        statusbuild.or(qm.status.eq(TaskState_Enum.DONE)).or(qm.status.eq(TaskState_Enum.CANCEL));
        builder= builder.and(qm.servu.id.eq(base.getUseu().getId())).andNot(statusbuild).and(qm.date.between(today,today.plusYears(1)))
                .and(qm.crman.isNull()).and(qm.bsType.eq(baseBusType));
        //旧的任务已经完成但是下检日期异常没同步的？旧任务的日期不在这后1年之内的而且还没终结的拖延的异常？（延期）要修改任务日。可能差错范畴。人工生成单子的。
        List<Task> tpool= (List<Task>) taskRepository.findAll(builder,sort);
        int poolOldSize= tpool.size();          //后面本次新生成任务可在tpool末尾加入！
        List<Task> tsnew=new ArrayList<>();       //新增加的任务
        Unit objServu= units.findById(base.getUseu().getId()).orElse(null);
        Assert.isTrue(null!=objServu, "无效单位"+base.getUseu().getId());
        log.info("SC{}:{}#{}${}" ,objServu.name(),basejyDate,tpool.size(), packs.stream().map(a->a.getCod()).collect(Collectors.toList()) );
        LocalDate finalBasejyDate = basejyDate;
        Function<Integer, Integer>  callback = val -> {
            return (int)((tpool.get(val).getDate().toEpochDay() - finalBasejyDate.toEpochDay()));
        };
        //从任务缓冲区挑选适合和当前下检日期吻合的可能任务：合理时间区间之内=管道前后60天定检，其他设备180天定检。依据来认定为同一次目标检验任务，避免重复生成。
        int innerDays = 60,  outerDays= 180;
        if(1==jyzq){             //年度检验:
            innerDays= 30;          //管道的特别 30天;
            outerDays= 90;
        }
        int resta[]= Tool.sortListMatchTwoYz(tpool.size()-1, callback,innerDays,outerDays);
        //准备好 60 : 180 天范围之内任务列表集合:两个范围区。 {管道+-1，年度+-3, #定检+-6 管道+-2，}匹配;
        int finalGuessofs1 = resta[1];
        int finalGuessend1 = resta[2];
        int finalGuessofs2 = resta[0];
        int finalGuessend2 = resta[3];
        BusinessCat_Enum finalBaseBusType = baseBusType;
        packs.forEach(eqpEs->{
            Assert.isTrue(base.getUseu().getId().equals(eqpEs.getUseu().getId()) && (0==jyzq && finalBasejyDate.equals(eqpEs.getNxtd2())
                   || 1==jyzq && finalBasejyDate.equals(eqpEs.getNxtd1()) ),"算法问题");
            Eqp eqp= eQPRepository.findById(eqpEs.getId()).orElse(null);
            log.info("排序asc作EqpEs {},{}:{}", eqpEs.getUseu().getId(), (jyzq==0? eqpEs.getNxtd2() : eqpEs.getNxtd1()), eqpEs.getCod());
            Division objDep=null;
            if(null!=eqpEs.getIspud())
                objDep= divisionRepository.findById(eqpEs.getIspud().getId()).orElse(null);
            if(null==objDep)    return;           //无效只能放弃！【前置的步骤的该干的事情】 部门都没设置啊！ 这儿不管。
            Unit objIspu=objDep.getUnit();        //不能直接依据eqpEs.getIspu(),  数据管理者不同！ 转义处理#
            Boolean  entrust= eqpEs.getOcat() || eqpEs.getReg().equals(RegState_Enum.TODOREG);
            Task  meet=null;
            Task  coincidence=null;     //最符合的任务：重复运行？同一批次的;
            TaskState_Enum lastCoinciSta= TaskState_Enum.CANCEL;        //最大的数值！
            //单个Eqp：可以放入某一个Task吗?{for task in tpool+new()}
            Boolean inGD= eqpEs.getType().equals("8");       //若管道设备的：合理时间时间区间更短暂！
            for (int i = 0; i < tpool.size(); i++) {
                //有必要搜索区间：
                if( i>=poolOldSize || (inGD && finalGuessofs1 >=0 && i>= finalGuessofs1 && i<= finalGuessend1)
                        || (!inGD && finalGuessofs2 >=0 && i>= finalGuessofs2 && i<= finalGuessend2)
                ) {
                    Task task = tpool.get(i);
                    //竟然task.getDep().equals(objDep)为假 但是task.getDep().getId().equals(objDep.getId())为真;
                    if(task.getIspu().getId().equals(objIspu.getId()) && task.getDep().getId().equals(objDep.getId()) && task.getEntrust().equals(entrust) ){
                        //多次出现某一个Eqp的同一种检验业务任务？  in｛ dets[].isp.dev.id=? ｝
                        if(task.getDets().stream().anyMatch(det ->
                                null!=det.getIsp() && null!=det.getIsp().getDev() && eqpEs.getId().equals(det.getIsp().getDev().getId()) )) {
                            meet = task;          //已经登记了业务，可能任务日期:不等于预期(下检)日期？
                            break;       //假定这么短时间内不会出现两次自动生成任务
                        }
                        else {
                            int xcdays= (int) (task.getDate().toEpochDay() - finalBasejyDate.toEpochDay());
                            if(0 == xcdays && TaskState_Enum.DISP!=task.getStatus() && TaskState_Enum.HANGUP!=task.getStatus()) {
                                if(lastCoinciSta.compareTo(task.getStatus()) >= 0 ) {
                                    coincidence = task;       //凑巧同一天的任务 & 允许新添加设备的。
                                    lastCoinciSta= task.getStatus();
                                }
                            }
                        }
                    }
                }
            }
            //合理时间区间之内没有遇见该设备的：代表可能要增加新任务或新设备到就任务的。
            if(null==meet){
                //虽然Eqp没有在可融合的任务当中，但是若有任务是碰巧能够接纳该设备？
                Detail detail=new Detail();
                Isp isp=Isp.builder().dev(eqp).servu(objServu).ispu(objIspu).bsType(finalBaseBusType).entrust(entrust).bus(detail).build();
                iSPRepository.save(isp);
                detail.setIsp(isp);
                if(null==coincidence) {          //无法仰仗旧的任务单的。 人工生成单子也不算：去重复只能放在分配派工环节剔除。
                    List<Detail> busList = new ArrayList<>();
                    busList.add(detail);
                    Task task = Task.builder().bsType(finalBaseBusType).servu(objServu).entrust(entrust).status(TaskState_Enum.INIT)
                            .date(finalBasejyDate).ispu(objIspu).dep(objDep).crtime(LocalDateTime.now()).dets(busList).build();
                    detail.setTask(task);
                    details.save(detail);        //保存这个时间task.id还没有啊?  事务
                    tpool.add(task);         //@新增加放入任务缓冲区末尾。
                    tsnew.add(task);
                }
                else{        //凑巧同一天的任务 & 已经派工的不能加：只能新生成new
                    coincidence.getDets().add(detail);
                    detail.setTask(coincidence);
                    details.save(detail);
                }
            }
        });
        //不做保存：报object references an unsaved transient instance save the transient instance before flushing : md.specialEqp.inspect.Detail.task -> md.specialEqp.inspect.Task
        taskRepository.saveAll(tsnew);
        return new Triple(retRes, null, null);       //批量保存不能用，本事务再次taskRepository.findAll(builder,sort)出错！
    }

    public Tuple fillVessel(Vessel eqp,VesselPrm pjc,VesselPrm pjy,Bigetly jcb,Bigetly jyb) {
        String retRes ="";
        VesselPrm  pms= null==pjc? pjy:pjc;
        if(null==pms)  return new Tuple("没参数表", eqp);
        Vessel.VesselBuilder<?, ?>  eqpBld=eqp.toBuilder();
        String oldsvp= eqp.getSvp();
        VesselSvp svp=JSON.parseObject(oldsvp, VesselSvp.class);
        String oldpa= eqp.getPa();
        VesselPam pam=JSON.parseObject(oldpa, VesselPam.class);
        eqpBld =eqpBld.vol(pms.getContainervolume()).prs(pms.getDespre()).pnum(null!=pjy? pjy.getCapablimitnum() : null)
                .highs(pms.getContainerheight()).weig(Tool.castFloat(pms.getTankcartowei())).rtlf(Tool.castFloat(pms.getLoadweig()))
                .fulw(Tool.castFloat(pms.getFullyloadwei())).mdi(pms.getTinamplmedi()).jakm(pms.getCovermedium())
                .form(pms.getCarstrform()).insul(pms.getTemppremode()).mont(pms.getInsform()).plat(pms.getCarsign());
        pms= null==pjy? pjc:pjy;     //非关键字段的:优先采信旧检验。
        svp= svp.toBuilder().结构(pms.getMainstrform()).支座(pms.getBasestyle()).是换热("换热面积".equals(pms.getIfContainervolume()))
                .内径(pms.getConinndia()).许工作压(pms.getPermPress()).许工作温(pms.getPermTemp()).许工作介(pms.getPermMedium())
                .设计介(pms.getDesignMedium()).壳设压(pms.getShelldesignpress()).壳设温(pms.getShelldesigntemperatrue()).壳介(pms.getShellmedium())
                .管设压(Tool.castFloat(pms.getTubedesignpress())).管设温(pms.getTubedesigntemperatrue()).管介(pms.getTubemedium())
                .夹设压(pms.getCoverdesignpress()).夹设温(pms.getCoverdesigntemperatrue()).筒厚(pms.getCanisterply()).头厚(pms.getSealply())
                .衬厚(pms.getInnerply()).夹厚(pms.getCoverply()).人均容(pms.getAvgArea()).舱容限(pms.getCapablimit()).人均(pms.getCapablimitEvery())
                .有保温("1".equals(pms.getYnheatpreins()) || "保温层".equals(pms.getYnheatpreins())).舱设压(Tool.castFloat(pms.getDesignpress()))
                .加压式(pms.getPressmode()).规格(pms.getVesselMod()).车空重(Tool.castFloat(pms.getKzzl())).罐材内(pms.getInnjarmat())
                .罐材外(pms.getOutjarmat()).罐材头(pms.getEnvjarmat()).罐设压(Tool.castFloat(pms.getTindesipress())).底盘号(pms.getTanjarbatnum())
                .罐设温(Tool.castFloat(pms.getTindesitemp())).罐厚筒(Tool.castFloat(pms.getBamjarwalthi())).罐厚头(Tool.castFloat(pms.getEnvjarwalthi()))
                .罐厚外筒(pms.getBamjarwalthio()).罐厚外头(pms.getEnvjarwalthio()).底盘型(pms.getBatholithmodel()).时速(pms.getDesignvelocityloadsurface())
                .时速弯(pms.getDesignvelocitycorner()).罐容积(pms.getTincubage()).人孔位(pms.getTinholeposi()).罐外内径(Tool.castFloat(pms.getTinoutlineinner()))
                .罐外壁厚(Tool.castFloat(pms.getTinoutlineply())).罐外长(Tool.castFloat(pms.getTinoutlinelength())).盖厚(Tool.castFloat(pms.getTopPly()))
                .盖材料(pms.getTopMeterial()).盖形式(pms.getTopMod()).筒料球(pms.getBodyMeterial()).壁厚(pms.getWallThick()).设规范(pms.getDesignLaw())
                .力类别(pms.getPresort()).封头型(pms.getSealtype()).是快开("是".equals(pms.getYnqopen())).监检式(pms.getInspectform())
                .制规范(pms.getManufacturecriterion())
                .build();
        pam= pam.toBuilder().充重(Tool.castFloat(pms.getLoadweight())).工作压(pms.getWorkPress()).工作温(pms.getWorkTemp())
                .工作介(pms.getWorkMedium()).筒料(pms.getSilomater()).封料(pms.getSealmater()).衬料(pms.getInnermater()).夹料(pms.getCovermater())
                .壳用压(pms.getShellusepress()).壳高压(Tool.castFloat(pms.getShelltoppress())).壳用温(pms.getShellusetemperatrue())
                .管用压(pms.getTubeusepress()).管高压(Tool.castFloat(pms.getTubetoppress())).管用温(pms.getTubetemperatrue())
                .夹用压(pms.getCoverusepress()).夹高压(Tool.castFloat(pms.getCovertoppress())).夹用温(pms.getCovertemperatrue())
                .筒腐裕(pms.getBodyRustGrade()).头腐裕(pms.getHeadRustGrade()).壳体重(pms.getChitinheft()).内件重(pms.getInnerheft())
                .舱高压(Tool.castFloat(pms.getTopworkpress())).舱用压(Tool.castFloat(pms.getUseworkpress())).空调式(pms.getAirconform())
                .照明(pms.getOxycablig()).测氧(pms.getMeaoxymod()).空调机(pms.getAirelemac()).表量程(pms.getPressurerange())
                .表精度(pms.getPressureapparprecision()).医疗登记(pms.getEnregisterno()).联合国号(pms.getJarundanno())
                .罐高压(Tool.castFloat(pms.getTiptoptem())).罐试压(Tool.castFloat(pms.getTanjarexapre())).功率(Tool.castFloat(pms.getEnginepower()))
                .侧稳定角(pms.getEmpsteaangel()).车形长(Tool.castFloat(pms.getWholecaroutlinelength())).车形宽(Tool.castFloat(pms.getWholecaroutlinewidth()))
                .车形高(Tool.castFloat(pms.getWholecaroutlineheigth())).轴荷前(pms.getFrfullaxisdist()).轴荷后(pms.getBafullaxisdist())
                .轴荷中(pms.getMifullaxisdist()).充系数(pms.getTinamplmodu()).充重量(Tool.castFloat(pms.getTinamplweig())).腐裕(pms.getTinerode())
                .保温料(pms.getTinheatpresmate()).热处(pms.getTinheattreafash()).耐试压(Tool.castFloat(pms.getTintestpress()))
                .气试压(pms.getTingastestpress()).铭牌位(pms.getNamplapos()).罐色(pms.getJarbodcor()).运行态(pms.getMotionSta())
                .安全(pms.getSecurityLev()).装卸位(pms.getLoaugropos()).装卸式(pms.getMediassemode()).危险介(pms.getDangerMedType())
                .build();
        eqp=(Vessel)eqpBld.svp(Tool.toJSONString(svp)).pa(Tool.toJSONString(pam)).build();
        return new Tuple(retRes, eqp);
    }
    public Tuple fillVessel常(Vessel eqp,VesselPrm pjc,VesselPrm pjy,Bigetly jcb,Bigetly jyb) {
        VesselPrm  pms= pjy;    //监察没有R000单独技术表
        if(null==pms)   return new Tuple("没有技术参数", eqp);    //=17条
        Vessel.VesselBuilder<?, ?>  eqpBld=eqp.toBuilder();
        String oldsvp= eqp.getSvp();
        VesselSvp svp=JSON.parseObject(oldsvp, VesselSvp.class);
        String oldpa= eqp.getPa();
        VesselPam pam=JSON.parseObject(oldpa, VesselPam.class);
        eqpBld =eqpBld.vol(pms.getCubage()).rtlf(Tool.castFloat(pms.getRatedLoad())).plat(pms.getVehicNum());
        svp= svp.toBuilder().车辆类(pms.getCarType()).车架(pms.getCjNo()).发动机(pms.getEngineCod()).build();
        pam= pam.toBuilder().介质(pms.getInnerMedium()).铭牌(pms.getNameplateState()).运输证(pms.getSendCert()).委托书(pms.getConsignNum())
                .外尺寸(pms.getShapeSize()).额定压(pms.getDesignpress()).价格(Tool.castFloat(pms.getEqpPrice())).桶数(pms.getTubSum())
                .桶规格(pms.getTubType()).修改人(pms.getEndModifyMan()).build();
        eqp=(Vessel)eqpBld.svp(Tool.toJSONString(svp)).pa(Tool.toJSONString(pam)).build();
        return new Tuple("", eqp);
    }
    /**起重机械的技术参数，从旧平台同步过来：总共172个字段。 pjc监察技术参数有可能来自TempXXX临时表;
     * */
    public Tuple fillCrane(Crane eqp,PcraneQzj pjc,PcraneQzj pjy,Bigetly jcb,Bigetly jyb) {
        String retRes ="";
        PcraneQzj  pms= null==pjc? pjy:pjc;      //注销登记+迁出的设备：参数表变成空的。
        if(null==pms)    return new Tuple("没有参数表", eqp);
        Crane.CraneBuilder<?, ?>  eqpBld=eqp.toBuilder();
        String oldsvp= eqp.getSvp();
        CraneSvp svp=JSON.parseObject(oldsvp, CraneSvp.class);
        String oldpa= eqp.getPa();
        CranePam pam=JSON.parseObject(oldpa, CranePam.class);
        String occa= null==jcb? jyb.getEqpUseOcca() : jcb.getEqpUseOcca();
        Float  pnumf=Tool.castFloat(pms.getBernum());
        eqpBld =eqpBld.nnor("是".equals(pms.getIfUnnormal())).rtlf(pms.getChaengloa()).vls(pms.getRatedspeed()).rvl(pms.getRunV())
                .mvl(pms.getLiftespeedmain()).cvl(pms.getLCarV()).scv(pms.getSCarV()).lmv(pms.getLanmovspe()).rtv(pms.getRotatesvelocity())
                .luff(pms.getAlterrangevelocity()).flo(pms.getTcFloornum()).pnum(null!=pnumf? pnumf.shortValue() : null)
                .hlfm(null!=pjy? pjy.getEleheightmain() : null).hlf(Tool.castFloat(pms.getEleheight())).rang(pms.getRange())
                .span(pms.getSpan()).two("是".equals(pms.getIfTwoCab())).twoc("是".equals(pms.getIfTwoLcar())).grab("是".equals(pms.getIfGrabB()))
                .suck("是".equals(pms.getIfSuctorial())).cotr("是".equals(pms.getIfContainH())).walk("是".equals(pms.getIfXzs()))
                .mom(pms.getChaadvmom()).whole(null!=pjy? "是".equals(pjy.getIfZjcc()) : null).pcs(pms.getTcCarsize()).pcw(pms.getTcCarweight())
                .miot(Tool.castFloat(pms.getTcIoMaxtime())).opm(pms.getOperStytle()).luf(pms.getAlterrangemode()).jobl(pms.getWorkgrade())
                .highs(pms.getTcEqphigh()).part(pms.getUpBody()).occa(occa).metl("是".equals(pms.getIfMetallurgy()))
                .cap(pms.getMaxratedcarrymass()).auxh("是".equals(pms.getIfViceLoad())).wjib("是".equals(pms.getIfViceArm()));
        pms= null==pjy? pjc:pjy;     //非关键字段的:优先采信旧检验。
        svp= svp.toBuilder().臂构式(pms.getQmBjstyle()).臂类(pms.getQmBjtype()).变幅施(pms.getQmAlterrangemode()).节联接(pms.getQmBzjlstyle())
                .层站数(pms.getLayerstage()).搭载式(pms.getCarryMethod()).底架型(pms.getBasetype()).吊臂型(pms.getCranearmtype())
                .钩时额量(pms.getDgMaxratedcarrymass()).钩时幅(pms.getDgRange()).斗时额量(Tool.castFloat(pms.getZdMaxratedcarrymass()))
                .斗时幅(pms.getZdRange()).钩时幅(pms.getDgRange()).专时额量(Tool.castFloat(pms.getZyMaxratedcarrymass())).专时幅(pms.getZyRange())
                .主吊具(pms.getQmMainupType()).额起升速(pms.getLiftespeedvalue()).制动载荷(pms.getRatbraloa()).防爆(pms.getExSign())
                .防爆级(pms.getExplosiveLevel()).附装名(pms.getAuxiName()).附装品(pms.getAuxiType()).工半径(Tool.castFloat(pms.getWorkradius()))
                .工环境(pms.getWorkcondition()).轨长(pms.getRaillenth()).轨长桥(pms.getTrackLen()).轨距(Tool.castFloat(pms.getGauge()))
                .回转(pms.getQmHzstyle()).架桥承(pms.getJqjRatebearing()).架设式(pms.getSpanmode()).架设跨(pms.getSpabesdia()).监检式(pms.getInspectform())
                .门跨度(pms.getSpanLen()).门结构(pms.getQmMjstyle()).平衡重(pms.getCounterbalance()).副钩高(pms.getEleheightvice()).起升速(pms.getLiftespeed())
                .副钩速(pms.getLiftespeedvice()).覆力矩(pms.getUpsetmoment()).设型式(pms.getQmEqpType()).设规范(pms.getDesigncriterion())
                .伸展(pms.getSpanStruct()).生产率(pms.getOutput()).升降速(pms.getUpDownSpeed()).是监("是".equals(pms.getIfJhsy()))
                .有附装("是".equals(pms.getIfAuxi())).有监控("是".equals(pms.getIfMonitor())).专导梁("是".equals(pms.getIfSupport()))
                .塔顶型(pms.getTiptowertype()).塔身型(pms.getTowertype()).出入口(pms.getTcInoutsize()).桅杆(pms.getCantilCons()).悬长(pms.getArmLen())
                .悬2长(pms.getArmLen2()).悬跨度(Tool.castFloat(pms.getArmSpan()))
                .车方法(pms.getRunMethod()).支腿(pms.getSuppLeg()).造规(pms.getManufacturecriterion()).梁结构(pms.getQmMaingridStyle())
                .梁数(pms.getQmMaingridNum()).主悬件(pms.getQmMainupUnit()).主升机构(pms.getQmMainupunitType()).主体式(pms.getMainstrform())
                .主腿构(pms.getQmMainlegStyle()).自重(Tool.castFloat(pms.getEmptyWeight())).组装型(pms.getQmZztype()).最幅起(pms.getMaxMlift())
                .最工幅(pms.getMaxworkrange()).build();
        Float  附着f=Tool.castFloat(pms.getQmAuxiNumIsping());
        pam= pam.toBuilder().锁编号(pms.getSaflocnum()).锁型号(pms.getSafloctyp()).爆区(pms.getExplAreaPlot()).爆物(pms.getExplMattType())
                .承索数(pms.getQmCzsNum()).馈电式(pms.getQmCarmode()).导支跨度(pms.getSpanSupport()).吊具型(pms.getNacelletype())
                .检吊具(pms.getQmMainupTypeIsping()).笼行程(pms.getSuscagworjour()).顶升速(Tool.castFloat(pms.getTopupvelocity()))
                .动力系统(pms.getPowSystem()).副钩1重(Tool.castFloat(pms.getChaengloavice())).副钩2重(pms.getChaengloavice2())
                .电方轻(pms.getPowerSupp()).纵移速(pms.getPormovspe()).回转角(pms.getRotatesveangle()).基距(Tool.castFloat(pms.getBaseDistance()))
                .集前伸(pms.getContainLen()).监系号(pms.getMonitorFactorycod()).监系单(pms.getMonitorUnitName()).监系型(pms.getMonitorType())
                .副臂长(pms.getArmLen2Isping()).副钩倍(pms.getQmRatioViceIsping()).副额起(pms.getQmRatedcarrymassvice())
                .检跨度(pms.getSpanIsping()).架铰高(Tool.castFloat(pms.getQmArmHighIsping())).所在地(pms.getEqpUseAddrIsping())
                .主臂长(pms.getArmLen1Isping()).主钩倍(pms.getQmRatioMainIsping()).工作幅(Tool.castFloat(pms.getQmMaxworkrangeIsping()))
                .小车数(pms.getQmLiftcarNum()).起重臂(pms.getLiftArm()).总重(Tool.castFloat(pms.getCarrymasstotalwei())).取物(pms.getFetchset())
                .所爆级(pms.getFbArealevel()).标高差(pms.getDiffBetween()).提力矩(pms.getLiftedmmoment()).提绳径(Tool.castFloat(pms.getLifteropediam()))
                .停车式(pms.getTcStytle()).深度(pms.getDownDeep()).降速(pms.getDownSpeed()).驶速度(pms.getRunningspeed()).走范围(pms.getMovrange())
                .走速度(pms.getMovingvelocity()).移动型(pms.getQmMovetype()).易爆物(pms.getFbSubstance()).效半径(Tool.castFloat(pms.getEffRadius()))
                .效重量(pms.getExpcarrymass()).车运行时(pms.getRunTime()).轨道(pms.getRunTrack()).总功(Tool.castFloat(pms.getWholemachinetotalpower()))
                .钩1重(pms.getChaengloamain()).钩2重(pms.getChaengloamain2()).余冲程(pms.getCypStr()).自由高(pms.getFreeehigh())
                .独立高(Tool.castFloat(pms.getQmMaxhigh())).外伸距(Tool.castFloat(pms.getMaxOutlen())).最小幅监(pms.getMinMargins())
                .最小幅(pms.getMinworkrange()).附着(null!=附着f? 附着f.shortValue() : null).build();
        eqp=(Crane)eqpBld.svp(Tool.toJSONString(svp)).pa(Tool.toJSONString(pam)).build();
        return new Tuple(retRes, eqp);
    }
    //先把监察PcraneQzj提取升降机所有有用字段转为检验的IlifsParg。方便统一处理：监察平台的升降机技术参数数据有效字段太少;
    public Tuple fillCrane升降机(Crane eqp,PcraneQzj pjcCra,IlifsParg pjy,Bigetly jcb,Bigetly jyb) {
        String retRes ="";
        IlifsParg  pjc=null;
        if(null!=pjcCra)    pjc=lifterConvertParmFromJC(pjcCra);
        IlifsParg  pms= null==pjc? pjy:pjc;
        if(null==pms)  return new Tuple("没参数表", eqp);
        Crane.CraneBuilder<?, ?>  eqpBld=eqp.toBuilder();
        String oldsvp= eqp.getSvp();
        CraneSvp svp=JSON.parseObject(oldsvp, CraneSvp.class);
        String oldpa= eqp.getPa();
        CranePam pam=JSON.parseObject(oldpa, CranePam.class);
        //字段.level() 起重 在基础表当中就做了设置；
        Float  flof=Tool.castFloat(pms.getFloorNum());
        Float  nsf=Tool.castFloat(pms.getStationNum());
        String occa= null==jcb? jyb.getEqpUseOcca() : jcb.getEqpUseOcca();
        eqpBld =eqpBld.flo(null!=flof? flof.shortValue() : null).vls(pms.getRatedV()).rtlf(Tool.castFloat(pms.getRatedLoad()))
                .jobl(pms.getWorkLevl()).opm(pms.getControlType()).cpi(pms.getConScreenCod()).cpm(pms.getConScreenType())
                .mom(pms.getMaxMoment()).nnor("是".equals(pms.getIfUnnormal())).twoc("是".equals(pms.getIfTwoHois()))
                .hlf(Tool.castFloat(pms.getUpHigh())).tm(pms.getDragType()).tno(pms.getDragCod()).ns(null!=nsf? nsf.shortValue() : null)
                .occa(occa);
        pms= null==pjy? pjc:pjy;     //非关键字段的:优先采信旧检验。
        Float  笼数f=Tool.castFloat(pms.getHoisNum());
        svp= svp.toBuilder().节联接(pms.getConnectType()).层门型(pms.getDoorPattern()).层门式(pms.getDoorStytle()).传动(pms.getDrivApproach())
                .葫芦号(pms.getHoistCod()).葫芦型(pms.getHoistType()).电机号(pms.getElecCod()).电动机类(pms.getElecStyle()).电机型(pms.getElecType())
                .笼数(null!=笼数f? 笼数f.shortValue() : null).对重块数(pms.getCoupNum()).对绳径(pms.getCoupRopDia()).额定载人(pms.getRatedPeople())
                .坠保护(pms.getFallProtect()).附属种(pms.getAuxiType()).工作式(pms.getOperStytle()).缓冲器(pms.getBufferMode())
                .限速器型(pms.getCarlimitVType()).门式(pms.getDoorCloseType()).门驱动(pms.getDoorDriveType()).门方向(pms.getDoorOpenDict())
                .伸展(pms.getSpanStruct()).是防爆("是".equals(pms.getIfExplosive())).绝缘("是".equals(pms.getIfInsulation()))
                .有附装("是".equals(pms.getIfAuxi())).拖动(pms.getDragMode()).绳数(pms.getDragNum()).绳直径(Tool.castFloat(pms.getDragDia()))
                .最大幅(Tool.castFloat(pms.getMargins())).最工幅(Tool.castFloat(pms.getTrackLen()))
                .build();
        Float  笼数检f=Tool.castFloat(pms.getHoisNumIsping());
        pam= pam.toBuilder().钳编号(pms.getSafeCod()).钳型号(pms.getSafeType()).电机功率(pms.getElecPower()).电机转速(pms.getElecRev())
                .笼行程(pms.getHoisTrip()).顶层高度(Tool.castFloat(pms.getTopHigh())).对重轨距(Tool.castFloat(pms.getCoupOrbDist()))
                .额定电流(pms.getRatedCurrent()).防爆标志(pms.getBlastsign()).坠安号(pms.getFallSaveCod()).坠安型(pms.getFallSaveType())
                .缓编号(pms.getBufferCod()).缓型号(pms.getBufferType()).缓厂家(pms.getBufferMakeUnt()).轨架高(Tool.castFloat(pms.getCounorbHeightIsping()))
                .笼数检(null!=笼数检f? 笼数检f.shortValue() : null).对块数检(pms.getCoupNumIsping()).附墙架(Tool.castFloat(pms.getWallNumIsping()))
                .轿厢轨距(Tool.castFloat(pms.getCarOrbDist())).下限电速(pms.getCarDownlimitEv()).下限机速(pms.getCarDownlimitMv())
                .限速器号(pms.getCarlimitVCod()).门数(Tool.castFloat(pms.getDoorNum())).环境(pms.getTaskCondition()).速比(pms.getVPropor())
                .提绳径(Tool.castFloat(pms.getUpRopDia())).限机械速(pms.getLimitMv()).限绳直径(Tool.castFloat(pms.getLimitRopDia()))
                .型试样(pms.getTestInfo()).曳引比(pms.getDragPropor()).轮节径(pms.getDragPitchDia()).用途(pms.getEqpUse()).自由高(Tool.castFloat(pms.getFeedHigh()))
                .独立高(Tool.castFloat(pms.getMostTop())).作业人(pms.getWorkMan())
                .build();
        eqp=(Crane)eqpBld.svp(Tool.toJSONString(svp)).pa(Tool.toJSONString(pam)).build();
        return new Tuple(retRes, eqp);
    }
    public Tuple fillBoiler(Boiler eqp,BoilerDat pjc,BoilerDat pjy,Bigetly jcb,Bigetly jyb) {
        String retRes ="";
        BoilerDat  pms= null==pjc? pjy:pjc;
        if(null==pms)  return new Tuple("没参数表", eqp);
        Boiler.BoilerBuilder<?, ?>  eqpBld=eqp.toBuilder();
        String oldsvp= eqp.getSvp();
        BoilerSvp svp=JSON.parseObject(oldsvp, BoilerSvp.class);
        String oldpa= eqp.getPa();
        BoilerPam pam=JSON.parseObject(oldpa, BoilerPam.class);
        eqpBld =eqpBld.wall(null!=pjy? "是".equals(pjy.getIfBoilWall()) : null).power(pms.getRatcon()).form(pms.getMainstrform())
                .fuel(pms.getBurningtype()).pres(pms.getDesworkpress()).bmod(pms.getBurnmode())
                .asemb("整组装".equals(pms.getFactoryType()));
        pms= null==pjy? pjc:pjy;     //非关键字段的:优先采信旧检验。
        svp= svp.toBuilder().加热方式(pms.getHeatupmode()).水循环(pms.getWatercircletype()).工作介质(pms.getWorkMedium())
                .安装况(pms.getInstCon()).炉房型(pms.getStokeholdtype()).汽水分离(pms.getGaswaterapartmode()).用途(pms.getSepurp())
                .主体材料(pms.getMainmate()).过热调温(pms.getSteamtemptype()).介名(pms.getMediumName()).介牌号(pms.getMediumCod())
                .介许温(Tool.castFloat(pms.getMediumAllowTemp())).予热器构(pms.getWarmupstrform()).燃器布置(pms.getBurntlaytype())
                .烧设备(pms.getBurequ()).设标准(pms.getDessta()).设出口温(pms.getDesexptem()).设出口压(pms.getDesexpstr()).设规范(pms.getDesigncriterion())
                .设挥发(pms.getBurntvolati()).设热效(pms.getDesithereffi()).设低热(Tool.castFloat(pms.getLowburnvalue())).设低热位(pms.getLowburnunit())
                .状态(pms.getUsestates()).水处式(pms.getWaterdealtype()).水处设型(pms.getWaterClMod()).制水能力(pms.getWoEqpPress())
                .水处造单(pms.getWaterEqpMakeUntName()).水源种(pms.getFousor()).烟尘式(pms.getSootavoidmode()).许用压(pms.getUsestr())
                .有载牌号(pms.getOrghcarbno()).许工温(pms.getAllwortem()).再热调温(pms.getResteamtemptype()).蒸汽用途(pms.getSteamfor())
                .制造范(pms.getManufacturecriterion()).build();
        pam= pam.toBuilder().饱和温度(Tool.castFloat(pms.getSattem())).补给水(pms.getAddwaterdealtype()).出口温(pms.getExpwortem())
                .出口压(pms.getExpworstr()).出热量(pms.getOutHot()).出水温度(Tool.castFloat(pms.getComwattem())).出渣(pms.getDrosstype())
                .除氧(pms.getDeoxidizemode()).司炉数(pms.getBoilernum()).电站情况(pms.getOthpara()).额定出力(pms.getHeatPow())
                .给水温(pms.getFeedwatertem()).给水压(pms.getFeewatstr()).工作温度(pms.getWorkTemp()).筒工作压(pms.getSiloworkpress())
                .过热温(pms.getSteamexporttem()).过热压(pms.getSteamexportpress()).回水温(pms.getBacwattem()).介出温(pms.getMediexporttemp())
                .介验日(pms.getMediumAssayDate()).试验介质(pms.getCompressTryMedium()).压验日(pms.getCompressTryDate())
                .试压力(Tool.castFloat(pms.getStrexmstr())).再热出温(Tool.castFloat(pms.getResteamexporttem()))
                .能效测标(pms.getNxcod()).能效评价(pms.getEffEval()).省煤构(pms.getPinchstrform()).蒸发量(Tool.castFloat(pms.getWorkPower()))
                .使用年限(Tool.castFloat(pms.getWorkAge())).有过热器("是".equals(pms.getSteamif()) || "有".equals(pms.getSteamif()))
                .使用压力(pms.getWorkPress()).受热布置(pms.getBeheatMachType()).水处模式(pms.getWaterYxms()).水压试日(pms.getWatPrsDate())
                .水压试力(pms.getWatPrs()).水油联话(pms.getUserWaLinkphone()).水油联人(pms.getUserWaLinkman()).水员数(pms.getWaterdealnum())
                .液验介(pms.getHydrDressTryMedium()).液验日(pms.getHydrDressTryDate()).液验压(pms.getHydrDressTryPress())
                .再热入温(Tool.castFloat(pms.getResteamimporttem())).再热出压(Tool.castFloat(pms.getResteamexportpress()))
                .再热入压(Tool.castFloat(pms.getResteamimportpress())).再汽流(Tool.castFloat(pms.getReheatflux()))
                .直启动流(Tool.castFloat(pms.getDireboilerstartflux())).直启动压(Tool.castFloat(pms.getDireboilerstartpress()))
                .最连蒸(pms.getMostvapovalue()).build();
        eqp=(Boiler)eqpBld.svp(Tool.toJSONString(svp)).pa(Tool.toJSONString(pam)).build();
        return new Tuple(retRes, eqp);
    }
    public Tuple fillFactoryVehicle(FactoryVehicle eqp,CarDrive pjc,CarDrive pjy,Bigetly jcb,Bigetly jyb) {
        String retRes ="";
        CarDrive  pms= null==pjc? pjy:pjc;
        if(null==pms)  return new Tuple("没参数表", eqp);
        FactoryVehicle.FactoryVehicleBuilder<?, ?>  eqpBld=eqp.toBuilder();
        String oldsvp= eqp.getSvp();
        FactoryVehicleSvp svp=JSON.parseObject(oldsvp, FactoryVehicleSvp.class);
        String oldpa= eqp.getPa();
        FactoryVehiclePam pam=JSON.parseObject(oldpa, FactoryVehiclePam.class);
        eqpBld =eqpBld.pow(pms.getDynamicmode()).rtlf(pms.getRatedloadweig()).mtm(pms.getEnginemodel());
        if(!StringUtils.hasText(eqp.getPlat()))   eqpBld.plat(pms.getCatlicennum());
        pms= null==pjy? pjc:pjy;     //非关键字段的:优先采信旧检验。
        svp= svp.toBuilder().牌型(pms.getBrandmodel()).防爆级(pms.getExplosiveLevel()).车类型(pms.getCartype()).厢数(pms.getCarNum())
                .传动(pms.getDrivApproach()).底盘号(pms.getBatnum()).电机号(pms.getMotornum()).机编号(pms.getEngnum()).机功率(pms.getEnginePower())
                .机转速(pms.getEngineV()).后轮距(pms.getRearTrack()).驾员(pms.getCabquota()).升最高(pms.getMaxlifheightWithoutLoad())
                .前轮距(pms.getFrontGauge()).燃料(pms.getBurkin()).设规范(pms.getDesigncriterion()).电压(pms.getSystemVoltage())
                .行装置(pms.getCarriDevic()).制规范(pms.getManufacturecriterion()).最坡度(pms.getMaxDriveSlope()).时速(pms.getTiptopmph())
                .build();
        pam= pam.toBuilder().环境(pms.getVehicUseEnv()).用区域(pms.getCarUseArea()).车空重(pms.getEmptyWeight()).胎型(pms.getTyreType())
                .厢座位(pms.getCarriageSeatsNum()).头座位(pms.getFrontSeatsNum()).引力(pms.getDragPower()).驱动(pms.getDriver())
                .油类(pms.getFueltype()).场防爆级(pms.getFbArealevel()).区域型(pms.getUseArea()).颜色(pms.getColor())
                .有拖("有".equals(pms.getTrailer())).速度(pms.getRunspeed()).载心距(pms.getLoadCenter())
                .build();
        eqp=(FactoryVehicle)eqpBld.svp(Tool.toJSONString(svp)).pa(Tool.toJSONString(pam)).build();
        return new Tuple(retRes, eqp);
    }
    public Tuple fillAmusement(Amusement eqp,YoxiPamj pjc,YoxiPamj pjy,Bigetly jcb,Bigetly jyb) {
        String retRes ="";
        YoxiPamj  pms= null==pjc? pjy:pjc;
        if(null==pms)  return new Tuple("没参数表", eqp);
        Amusement.AmusementBuilder<?, ?>  eqpBld=eqp.toBuilder();
        String oldsvp= eqp.getSvp();
        AmusementSvp svp=JSON.parseObject(oldsvp, AmusementSvp.class);
        String oldpa= eqp.getPa();
        AmusementPam pam=JSON.parseObject(oldpa, AmusementPam.class);
        Bigetly base= null==jcb? jyb:jcb;
        String level=null;
        if(StringUtils.hasText(base.getEqpLevel())){
            if("A".equals(base.getEqpLevel()))      level="A级";
            else if("B".equals(base.getEqpLevel()))     level="B级";
            else if("C".equals(base.getEqpLevel()))     level="C级";
        }
        eqpBld =eqpBld.level(level).angl(pms.getSwingangle()).leng(pms.getLength()).pnum(pms.getRatedpassengernum())
                .vl(pms.getRatedvelocity()).sdia(pms.getTurningdiameter()).grad(pms.getGrade()).mbig("是".equals(pms.getIfShift()))
                .high(pms.getHeight()).hlf(Tool.castFloat(pms.getMovHigh()));
        pms= null==pjy? pjc:pjy;     //非关键字段的:优先采信旧检验。
        svp= svp.toBuilder().半径(pms.getRadii()).蹦极(pms.getJumpType()).场面积(Tool.castFloat(pms.getCarageArea())).电压(pms.getPressure())
                .额载荷(pms.getRatedload()).副速度(pms.getSubvelocity()).绳直径(pms.getWireropedia()).高差(pms.getHeigdiff()).轨长(pms.getRaiwaylen())
                .轨高(pms.getTrackheigh()).轨数(pms.getRaiwaynum()).轨矩(pms.getGauge()).提送(pms.getSlidewayMachine()).道种类(pms.getSlidewayType())
                .道材索根(pms.getSlidewayStuff()).索数(pms.getSlideNum()).回收式(pms.getBackType()).驱动式(pms.getDriveform())
                .设备高(Tool.castFloat(pms.getEqpHigh())).设规范(pms.getDesigncriterion()).深度(pms.getDepth()).小蹦("是".equals(pms.getIfMinJump()))
                .池型(pms.getPoolType()).池深(pms.getCarniewterdeep()).制规范(pms.getManufacturecriterion()).舱高(pms.getSeatheight())
                .舱数(pms.getSeacabnum()).build();
        Float 线速度= null!=pjc? pjc.getLinevelocity() : null!=pjy? pjy.getLinevelocity() : null;
        pam= pam.toBuilder().车数(pms.getCarShpNum()).电动机转速(pms.getEleMotoRev()).副功率(pms.getSubpower()).轨矩长(pms.getGaugelength())
                .回收装(pms.getRecovery()).主功率(pms.getDrivemainpower()).滑梯高(pms.getWaterslideheight()).平距(Tool.castFloat(pms.getLineWidth()))
                .线速度(线速度).圆速度(pms.getCirspe()).直半径(pms.getDiarad()).build();
        eqp=(Amusement)eqpBld.svp(Tool.toJSONString(svp)).pa(Tool.toJSONString(pam)).build();
        return new Tuple(retRes, eqp);
    }
    public Tuple fillPipeline(Pipeline eqp,LineNotsen pjc,LineNotsen pjy,Bigetly jcb,Bigetly jyb) {
        String retRes ="";
        LineNotsen  pms= null==pjc? pjy:pjc;
        Pipeline.PipelineBuilder<?, ?>  eqpBld=eqp.toBuilder();
        String oldsvp= eqp.getSvp();
        PipelineSvp svp=JSON.parseObject(oldsvp, PipelineSvp.class);
        String oldpa= eqp.getPa();
        PipelinePam pam=JSON.parseObject(oldpa, PipelinePam.class);
        if(null!=pjy && !StringUtils.hasText(eqp.getTitl())) {    //3个地方来源
            if(StringUtils.hasText(pjy.getBoxName()))     eqpBld.titl(pjy.getBoxName());
            else  eqpBld.titl(null!=jcb? jcb.getEqpName() : jyb.getEqpName());
        }
        if(null==pms) {
            eqp=(Pipeline)eqpBld.build();
            return new Tuple("没装置参数表", eqp);    //但可以有单元
        }
        eqpBld =eqpBld.level(pms.getPipelineLevel()).matr(pms.getPipelineMedium()).mdi(pms.getWorkMedium()).temp(pms.getDesignTemp())
                .prs(pms.getDesignPress());
        pms= null==pjy? pjc:pjy;     //非关键字段的:优先采信旧检验。
        String  设备名=null!=jcb? jcb.getEqpName(): null!=jyb? jyb.getEqpName():null;   //主表EQP_NAME字段
        svp= svp.toBuilder().保温式(pms.getAdiabaticType()).腐材料(pms.getEmbalmment()).腐裕量(pms.getRotAmount()).规许可证(pms.getAllowProjCod())
                .工作温(pms.getWorkTemp()).工作压(pms.getWorkPress()).公壁厚(pms.getNominalPly()).公直径(pms.getNominalDia()).衬厚(pms.getInnerPly())
                .起点(pms.getStartPlace()).止点(pms.getEndPlace()).设证号(pms.getDisCertorgCod()).管道设备名(设备名).最高温(pms.getTopWorkTemp())
                .最高压(pms.getTopWorkPress()).附属设(null!=jyb? jyb.getIfFsEqp() : null).build();
        pam= pam.toBuilder().保装数(pms.getSafeMecNum()).安全级(pms.getSafeLevel()).腐施单(pms.getAntisepsisUnt()).根数(pms.getPipeNum())
                .总长(pms.getLength()).敷设(pms.getLayMode()).管规(pms.getPipelineSpec()).管厚(pms.getPipelinePly()).管材牌(pms.getPipelineDatumSign())
                .焊口数(pms.getHkNum()).绝热材(pms.getAdiabaticMedium()).绝热厚(pms.getAdiabaticPly()).衬料(pms.getInnerMedium())
                .实用时(pms.getUseTime()).试压(pms.getTryPress()).监检号(pms.getWarrantCod()).输送介(pms.getSendMedium())
                .探伤比(pms.getFalwRadio()).保温(pms.getIfSavetemp()).build();
        eqp=(Pipeline)eqpBld.svp(Tool.toJSONString(svp)).pa(Tool.toJSONString(pam)).build();
        return new Tuple(retRes, eqp);
    }
    /**
     AK之后需剔除无效的设备代码组合。{mysql加索引,否则很慢!}; EQP_REG_STA:"3" &&EQP_USE_STA:"8"的设备还需要保留：是否非注册设备=1检验可以查询 EQP_COD='3505R27707'。
     若结果'多个:？读实体失败'，单独再重做需要复制多条关联数据再运行。 确认过后：可以全部删除本轮产生fail!=null数据=全部都清空(针对福建省特检院检验平台来说,厦门有点毛病)。
     select cod？oid,count(*) from storesync9 group by cod？oid order by count(*) desc：还是有不正常数据！若是能需要提前处理？
     [例外@]一个cod竟然对应两个oid?两次注册(也没注销)都是一个设备。 一个设备两个cod:一个待注册一个已注册。 一个设备在检验和监察给出的cod并不一样{不算厦门}。
     */
    public <T> Triple  makeFor单TZ设备过滤Kg(T inObj) {
        StoreSync eqp= (StoreSync)inObj;
        if(StringUtils.hasText(eqp.getCod())) {
            List<StoreSync> all= storeSyncRepository.findAllByCod(eqp.getCod());
            if(all.size()>1){
                if(!StringUtils.hasText(eqp.getOid()))   return new Triple("没用oid空", null, null);
                for (StoreSync one: all) {
                    if(!eqp.getOid().equals(one.getOid()) && StringUtils.hasText(one.getOid()) ) {
                        String cond="OIDNO='"+eqp.getOid()+"'";
                        Bigetly  bs=null;
                        ReadComm<Bigetly> jc= Utilities.getItArrType(Bigetly.class,201,cond);
                        if(null==jc.getArr() )    return new Triple("oid多个:读实体失败" + one.getOid(), null, null);
                        else if(jc.getArr().size()>0)      bs=jc.getArr().get(0);
                        Boolean ok=false;
                        if(null!=bs && null!=bs.getEqpRegSta() && null!=bs.getEqpUseSta() && 3!=bs.getEqpRegSta()
                                && 4!=bs.getEqpUseSta() && ( bs.getEqpUseSta()<6 || bs.getEqpUseSta()>8 ) )
                            ok=true;
                        if(!ok)  return new Triple("oid多个:非法," + one.getOid(), null, null);
                    }
                }
            }
        }
        if(StringUtils.hasText(eqp.getOid())) {
            List<StoreSync> all= storeSyncRepository.findAllByOid(eqp.getOid());
            if(all.size()>1){
                if(!StringUtils.hasText(eqp.getCod()))   return new Triple("没用cod空", null, null);
                for (StoreSync one: all) {
                    if(!eqp.getCod().equals(one.getCod()) && StringUtils.hasText(one.getCod()) ){
                        String cond="EQP_COD='"+eqp.getCod()+"'";
                        Bigetly  bs=null;
                        ReadComm<Bigetly> jy= Utilities.getItArrType(Bigetly.class,1,cond);
                        if(null==jy.getArr() )    return new Triple("cod多个:读实体失败" + one.getCod(), null, null);
                        else if(jy.getArr().size()>0)      bs=jy.getArr().get(0);
                        Boolean ok=false;
                        if(null!=bs && null!=bs.getEqpRegSta() && null!=bs.getEqpUseSta() && 3!=bs.getEqpRegSta()
                                && 4!=bs.getEqpUseSta() && ( bs.getEqpUseSta()<6 || bs.getEqpUseSta()>8 ) )
                            ok=true;
                        //厦门的设备也会可能在旧检验平台出现的：EQP_COD和监察平台看见厦门cod不一样。厦门自己cod却找不到旧检验平台数据bs==null。导致厦门设备技术参数采用旧检验平台数据。
                        if(!ok) {
                            if(null==bs) {      //厦门？，cod却找不到旧检验平台数据
                                cond="OIDNO='"+eqp.getOid()+"'";
                                bs=null;
                                ReadComm<Bigetly> jc= Utilities.getItArrType(Bigetly.class,201,cond);
                                if(null==jc.getArr() )    return new Triple("cod多个:非法,读实体失败" + one.getCod(), null, null);
                                else if(jc.getArr().size()>0)      bs=jc.getArr().get(0);
                                if(null!=bs && null!=bs.getEqpAreaCod()){
                                    String area = bs.getEqpAreaCod().substring(0, 4);
                                    if("3502".equals(area))    return new Triple("cod多个:非法,厦门?" + one.getCod(), null, null);
                                }
                            }
                            return new Triple("cod多个:非法," + one.getCod(), null, null);    //厦门编出EQP_COD&&同时我方也有的就带来问题; 优先用我方cod !。
                        }
                    }
                }
            }
        }
        return new Triple("", null, null);
    }
    public Tuple syncPipeDevice(Pipeline eqp,List<DyguanDao> jc,List<DyguanDao> jy) {
        int jcSize= null!=jc? jc.size() :0;
        int jySize= null!=jy? jy.size() :0;
        if(jcSize<=0 && jySize<=0)  return new Tuple("没单元", eqp);    //11815条
        if(jcSize+jySize>1000)      log.info("单元数{},{}", jcSize,jySize);
        Map<String, DyguanDao>  mregc= new HashMap<>();     //监察的;有登记码的：EQP_UNT_REGCOD 做唯一性表示符
        Map<String, DyguanDao>  mregy= new HashMap<>();     //不是线程安全的 synchronize;
        Map<String, DyguanDao>  murc= new HashMap<>();    //EQP_CODE, START_PLACE, END_PLACE, BOX_NAME+rno ==唯一性
        Map<String, DyguanDao>  mury= new HashMap<>();      //旧检验平台:最多的,包含无登记代码的；
        Map<String, PipingUnit>  mcell= new HashMap<>();     //本平台现成的。
        if(jcSize>0) {
            ListIterator<DyguanDao> it = jc.listIterator();
            while (it.hasNext()) {
                DyguanDao et = it.next();
                if (StringUtils.hasText(et.getEqpUntRegcod()))      mregc.put(et.getEqpUntRegcod(), et);
                murc.put(mapHashKey(et), et);
            }
        }
        if(jySize>0) {
            ListIterator<DyguanDao> it = jy.listIterator();
            while (it.hasNext()) {
                DyguanDao et = it.next();
                if (StringUtils.hasText(et.getEqpUntRegcod()))      mregy.put(et.getEqpUntRegcod(), et);
                mury.put(mapHashKey(et), et);
            }
        }
        if(null==eqp.getCells())    eqp.setCells(new LinkedList<>());
        ListIterator<PipingUnit>  it =eqp.getCells().listIterator();
        while (it.hasNext()) {
            PipingUnit et = it.next();
            if (StringUtils.hasText(et.getRno()) && null==mregc.get(et.getRno()) && null==mregy.get(et.getRno()) ) {
                it.remove();    //有登记码 确实消失掉。
                pipingUnitRepository.delete(et);
            }
        }
        it =eqp.getCells().listIterator();
        while (it.hasNext()) {
            PipingUnit et = it.next();
            String plukey=mapHashKey(et);
            if (null==murc.get(plukey) && null==mury.get(plukey) ) {
                it.remove();    //两个平台都没，删除
                pipingUnitRepository.delete(et);
            }
        }
        it =eqp.getCells().listIterator();
        while (it.hasNext()) {
            PipingUnit et = it.next();
            String plukey=mapHashKey(et);
            Assert.isTrue(null==mcell.get(plukey),"Hash重复Key");
            mcell.put(plukey, et);   //转为Hash避免 重复的Key@
        }
        PipingUnitBuildContext pipingContext=new PipingUnitBuildContext();
        pipingContext.setPipe(eqp);
        List<PipingUnit> batch=new ArrayList<PipingUnit>();     //检验队列首先走：
        Iterator<Map.Entry<String, DyguanDao>>  itr= mury.entrySet().iterator();
        while (itr.hasNext()) {
            Map.Entry<String, DyguanDao>  entry = itr.next();
            String plukey=entry.getKey();
            PipingUnit  myu=mcell.get(plukey);
            DyguanDao  jcu=murc.get(plukey);
            if(null==myu)   myu=new PipingUnit();
            if(null==jcu)   myu.setNinsp(true);
            myu= fillPipeUnitPrm(myu,jcu,entry.getValue(),pipingContext);
            myu.setPipe(eqp);
            batch.add(myu);
            eqp.getCells().add(myu);
        }
        itr= murc.entrySet().iterator();
        while (itr.hasNext()) {
            Map.Entry<String, DyguanDao>  entry = itr.next();
            String plukey=entry.getKey();
            DyguanDao  jyu=mury.get(plukey);
            if(null==jyu) {     //检验已经做了 ,监察最后补充。
                PipingUnit myu = mcell.get(plukey);
                if (null == myu)    myu = new PipingUnit();
                myu = fillPipeUnitPrm(myu, entry.getValue(), null,pipingContext);
                myu.setPipe(eqp);
                batch.add(myu);
                eqp.getCells().add(myu);
            }
        }
        pipingUnitRepository.saveAll(batch);
        int umFailSum=0;
        StringBuilder  sbuilder = new StringBuilder(512);
        sbuilder.append("没找到单位:");
        Iterator<Map.Entry<String, Unit>>  ctxuit= pipingContext.getMunit().entrySet().iterator();
        while (ctxuit.hasNext()) {
            Map.Entry<String, Unit>  entry = ctxuit.next();
            if(null==entry.getValue() && StringUtils.hasText(entry.getKey()) ) {
                sbuilder.append(entry.getKey()).append(';');
                umFailSum++;
            }
        }
        if(umFailSum>0)  return new Tuple(sbuilder.toString(), eqp);
        return new Tuple("", eqp);
    }
    //管道单元 参数表: 不一致？关键字段采信监察。比如管道装置OIDNO='DE07145'在检验是垃圾数据在监察却是在用的！听谁啊
    public PipingUnit fillPipeUnitPrm(PipingUnit pip,DyguanDao jcp,DyguanDao jyp,PipingUnitBuildContext ctx) {
        DyguanDao bsp= null!=jcp? jcp: jyp;
        PipingUnit.PipingUnitBuilder  pipBld=pip.toBuilder();
        String areaCod= null!=jcp? jcp.getEqpAreaCod() : jyp.getUnitAreaCod();
        Adminunit adminunit=null;
        if(!ctx.getMadm().containsKey(areaCod)) {
            adminunit = adminunitRepository.findTopByAreacode(areaCod);
            ctx.getMadm().put(areaCod, adminunit);   //短暂缓存的数据库查询,同一次session连续批量同步该装置所有管道单元。
        }
        else  adminunit=ctx.getMadm().get(areaCod);
        String insuName=bsp.getInstUnit();
        Unit insUnit=null;
        if(!ctx.getMunit().containsKey(insuName)) {
            insUnit= units.findUnitByCompany_Name(insuName);    //Hibernate infinispan有可能缓存了；
            ctx.getMunit().put(insuName, insUnit);
        }
        else  insUnit=ctx.getMunit().get(insuName);
        String desuName=bsp.getDesignUnit();
        Unit desUnit=null;
        if(!ctx.getMunit().containsKey(desuName)) {
            desUnit= units.findUnitByCompany_Name(desuName);
            ctx.getMunit().put(desuName, desUnit);
        }
        else  desUnit=ctx.getMunit().get(desuName);
        Isp isp1=null, isp2=null, ispsv=null;   //根据报告编号定位：首次看见的结论+检验日期数据也 是按照首次见的为准。
        //监督检验 INCP_ISP_REPORT_COD INCP_ISP_CONCLU INCP_ISP_DATE
        Isp.IspBuilder ispBuilder;
        if (StringUtils.hasText(bsp.getIncpIspReportCod())) {
            if(!ctx.getMisp().containsKey(bsp.getIncpIspReportCod())) {
                ispsv = iSPRepository.findTopByDevAndNo(ctx.getPipe(), bsp.getIncpIspReportCod());
                if (null == ispsv) {
                    ispBuilder = new Isp().toBuilder().dev(ctx.getPipe()).no(bsp.getIncpIspReportCod());
                } else ispBuilder = ispsv.toBuilder();   //第二次同步Eqp发现已生成过了
                ispsv = ispBuilder.ispDate(null != bsp.getIncpIspDate() ? bsp.getIncpIspDate().toLocalDate() : null)
                        .conclusion(bsp.getIncpIspConclu()).bsType(BusinessCat_Enum.INSTA).build();  //管道单元没有指定业务类型。
                ispsv=iSPRepository.save(ispsv);
                ctx.getMisp().put(bsp.getIncpIspReportCod(), ispsv);
            }
            else  ispsv=ctx.getMisp().get(bsp.getIncpIspReportCod());
        }
        //定期(全面) ISP_REPORT_COD ISP_CONCLU ISP_DATE
        if (StringUtils.hasText(bsp.getIspReportCod())) {
            if(!ctx.getMisp().containsKey(bsp.getIspReportCod())) {
                isp2 = iSPRepository.findTopByDevAndNo(ctx.getPipe(), bsp.getIspReportCod());    //数据库找出的;
                if (null == isp2) {
                    ispBuilder = new Isp().toBuilder().dev(ctx.getPipe()).no(bsp.getIspReportCod());
                } else ispBuilder = isp2.toBuilder();
                isp2 = ispBuilder.ispDate(null != bsp.getIspDate() ? bsp.getIspDate().toLocalDate() : null)
                        .conclusion(bsp.getIspConclu()).bsType(BusinessCat_Enum.REGUL).build();
                isp2=iSPRepository.save(isp2);    //必须替换为返回对象：否则外部saveAll(batch)报错:detached entity passed to persist
                ctx.getMisp().put(bsp.getIspReportCod(), isp2);
            }
            else  isp2=ctx.getMisp().get(bsp.getIspReportCod());     //临时批量同步的短暂缓存
        }
        //年度在线 YEAR_ISP_REPORT_COD YEAR_ISP_CONCLU YEAR_ISP_DATE
        if (StringUtils.hasText(bsp.getYearIspReportCod())) {
            if(!ctx.getMisp().containsKey(bsp.getYearIspReportCod())) {
                isp1 = iSPRepository.findTopByDevAndNo(ctx.getPipe(), bsp.getYearIspReportCod());
                if (null == isp1) {
                    ispBuilder = new Isp().toBuilder().dev(ctx.getPipe()).no(bsp.getYearIspReportCod());
                } else ispBuilder = isp1.toBuilder();
                isp1 = ispBuilder.ispDate(null != bsp.getYearIspDate() ? bsp.getYearIspDate().toLocalDate() : null)
                        .conclusion(bsp.getYearIspConclu()).bsType(BusinessCat_Enum.ANNUAL).build();
                isp1=iSPRepository.save(isp1);
                ctx.getMisp().put(bsp.getYearIspReportCod(), isp1);
            }
            else  isp1=ctx.getMisp().get(bsp.getYearIspReportCod());
        }
        String laym=null!=jyp? jyp.getVLayMode() : null!=jcp? jcp.getLayMode() : null;
        if(!StringUtils.hasText(laym))   laym="";
        else {
            int jk=laym.indexOf("架空");
            int md=laym.indexOf("埋地");
            laym= (jk>=0 && md>=0)? "埋地+架空" : jk>=0 ? "架空" : md>=0 ? "埋地" : "其它";
        }
        pipBld =pipBld.rno(bsp.getEqpUntRegcod()).code(bsp.getEqpCode()).start(bsp.getStartPlace()).stop(bsp.getEndPlace())
                .name(bsp.getBoxName()).reg(RegState_Enum.values()[null!=bsp.getEqpRegSta()? bsp.getEqpRegSta() : 0])
                .ust(UseState_Enum.values()[null!=bsp.getUseSta()? bsp.getUseSta() : 9]).dia(null!=jyp? jyp.getVPipelineDia() : null)
                .regd(null!=bsp.getRegDate()? bsp.getRegDate().toLocalDate() : null).proj(bsp.getProjName())
                .thik(bsp.getNominalPly()).leng(null!=bsp.getLength()? bsp.getLength().floatValue() : null)
                .level(null!=jyp? jyp.getVPipelineLevel() : null!=jcp? jcp.getPipelineLevel() :null)
                .lay(laym).matr(null!=jyp? jyp.getVPipelineMedium() : null)
                .nxtd1(null!=bsp.getYearNextIspDate()? bsp.getYearNextIspDate().toLocalDate() : null)
                .nxtd2(null!=bsp.getNextIspDate()? bsp.getNextIspDate().toLocalDate() : null).mdi(bsp.getSendMedium())
                .used(null!=bsp.getEqpFinmakeDate()? bsp.getEqpFinmakeDate().toLocalDate(): null).ad(adminunit)
                .insd(null!=bsp.getEqpInstDate()? bsp.getEqpInstDate().toLocalDate() : null).safe(bsp.getSafeLevel())
                .insu(insUnit).desu(desUnit).isp1(isp1).isp2(isp2).ispsv(ispsv);
        String oldsvp= pip.getSvp();
        PipingUnitSvp svp=JSON.parseObject(oldsvp, PipingUnitSvp.class);
        if(null==svp)   svp=new  PipingUnitSvp();
        String oldpa= pip.getPa();
        PipingUnitPam pam=JSON.parseObject(oldpa, PipingUnitPam.class);
        if(null==pam)   pam=new  PipingUnitPam();
        svp= svp.toBuilder().监下(null!=bsp.getIncpNextIspDate()? bsp.getIncpNextIspDate().toLocalDate() : null).规格(bsp.getPipelineSpec())
                .设压(bsp.getDesignPress()).设温(bsp.getDesignTemp()).腐蚀(bsp.getRotAmount()).焊数(bsp.getHkNum()).材料(bsp.getPipelineMedium())
                .直径(bsp.getNominalDia()).build();
        bsp= null==jyp? jcp:jyp;     //pam:非监察关心的字段：优先采信旧检验平台的数据。
        pam= pam.toBuilder().工压(bsp.getWorkPress()).试压(bsp.getTryPress()).工温(bsp.getWorkTemp()).防腐材(bsp.getEmbalmment())
                .沥青级(bsp.getEmbalmLevel()).防腐厚(bsp.getEmbalmHd()).工介(bsp.getWorkMedium()).实用时(bsp.getUseTime())
                .绝材(bsp.getAdiabaticMedium()).绝厚(bsp.getAdiabaticPly()).保数(bsp.getSafeMecNum()).备注(bsp.getMemo())
                .图号(bsp.getPicNo()).build();
        pip=(PipingUnit)pipBld.svp(Tool.toJSONString(svp)).pa(Tool.toJSONString(pam)).build();
        return pip;
    }
    public Boolean setupFromLegacy_DP检验HR部门(String arg) {
        if(!emFjtj.isJoinedToTransaction())     emFjtj.joinTransaction();
        System.out.println("设备检验HR部门开始：" +new Date());
        ReadComm<JianyanBumen> result= Utilities.getItArrType(JianyanBumen.class,26,"1=1");
        int countall=result.getArr().size();
        log.info("归属部门: 条数{},服务应答{}", countall,result.getErrorCode());
        List<HrDeptinfo> batch=new ArrayList<>();
        for (Object one1: result.getArr())
        {
            JianyanBumen one= (JianyanBumen)one1;
            String json = JSON.toJSONString(one);
            HrDeptinfo obj=JSON.parseObject(json, HrDeptinfo.class);
            batch.add(obj);
        }
        hrDeptinfoRepository.saveAll(batch);
        log.info("HR归属部门:完成， 条数{}", countall);
        return true;
    }
    public Boolean setupFromLegacy_Pz部门生成(String arg) {
        if(!emFjtj.isJoinedToTransaction())     emFjtj.joinTransaction();   //依照HR部门生成导Division
        Unit meu= units.findUnitByCompany_Name("福建省特种设备检验研究院");
        Assert.isTrue(null!=meu,"没初始化");
        List<HrDeptinfo> result=hrDeptinfoRepository.findAll();
        List<Division> batch=new ArrayList<>();
        for (HrDeptinfo one: result)
        {
            if(one.getUSE_STA()!=1)  continue;
            Division division= divisionRepository.findByUnitAndName(meu, one.getDEPT_NAME());
            if(null==division)  division=new Division();
            Adminunit adminunit=adminunitRepository.findTopByAreacode(one.getAREA_COD());
            division= division.toBuilder().name(one.getDEPT_NAME()).unit(meu).address(one.getDEPT_ADD())
                    .linkMen(one.getDEPT_PRINC()).phone(one.getDEPT_TEL()).ad(adminunit)
                    .branch(false).frot("闽HR").build();
            batch.add(division);
            one.setDivision(division.getId());    //JPA关联关系不能跨越多个数据库.
        }
        divisionRepository.saveAll(batch);
        hrDeptinfoRepository.saveAll(result);    //没有这一句：会导致需要运行两遍才能保存 hrDep.division=@id；
        log.info("归属部门:完成， 条数{}", batch.size());
        return true;
    }
    public Boolean syncFromLegacy_AU监单位普cM(String arg) {
        if(!emIncp.isJoinedToTransaction())      emIncp.joinTransaction();
        Assert.isTrue(emIncp.isJoinedToTransaction(),"没emIncp Transaction");
        ComUnit allunit=new ComUnit();
        String mask =Utilities.reflectDbItems(allunit);
        String cond="1=1";
        log.info("syncFromLegacy_AU监单位普cM:webs arg={},过滤{}",arg, cond);
        ReadComm<ComUnit> result= Utilities.catchArrType(ComUnit.class,213,cond,mask);
        int countall=result.getArr().size();        //一次搞定174163条：运行时长还在可忍受范围。
        //终端交互才会用System.out.println((new String()).format("例行Utilities.average运行=%d",result.getErrorCode()) );
        log.info("syncFromLegacy_AU监单位普cM:type={},条数{},服务应答{}","AU Arg="+arg, countall,result.getErrorCode());
        return  syncFromLegacy_AU监单位普cM_DB(arg, result);
    }
    /**CRDB数据库事务经常会失败还会重做的*/
    @Transactional(propagation = REQUIRES_NEW)
    public Boolean syncFromLegacy_AU监单位普cM_DB(String arg,ReadComm<ComUnit> result) {
        if(!emIncp.isJoinedToTransaction())      emIncp.joinTransaction();
        Assert.isTrue(emIncp.isJoinedToTransaction(),"没emIncp Transaction");
        int countall=result.getArr().size();
        log.info("syncFromLegacy_AU监单位普cM_DB:type={},条数{},服务应答{}","AU Arg="+arg, countall,result.getErrorCode());
        int nowcnt=0;
        int lopct=0;
        List<JcUntMge> batch=new ArrayList<>();
        for (Object one1: result.getArr())
        {
            ComUnit one= (ComUnit)one1;
            String json = JSON.toJSONString(one);       //支持自循环嵌套不报错
            JcUntMge obj=JSON.parseObject(json, JcUntMge.class);
            lopct++;
            nowcnt++;
            batch.add(obj);
            if(lopct>=800) {        //每一批800条，但没提交，无法查询到的，虽然实际已写入数据库磁盘文件中。
                jcUntMgeRepository.saveAll(batch);
                lopct=0;
                batch.clear();
                log.info("syncEqpFromOld:do数{}",nowcnt);
            }
        }
        jcUntMgeRepository.saveAll(batch);    //每秒862条插入速度，但是运行到这里事务实际上还没结束：实际耗时还应该包括提交阶段耗时。
        log.info("syncFromLegacy_AU监单位普cM:完成，arg={},条数{}",arg, countall);
        return true;
    }
    /**分片作业+批量保存的模式； mng是输入状态，jsliceMang代表修改的状态；
     * 同时兼容ElasticsearchRepository<T, ID>以及JpaRepository<T, ID>;在boot3.0版本需要区分加入< ,RP>>类型；
     * 泛型矛盾检查： 这里的T可能这样的JcPermtUnt implements SliceSyncRes； 所以T extends SliceSyncRes,以及Function<SliceSyncRes, Triple> sliceJob,
     * 避免和后面RP extends PagingAndSortingRepository<T,?> & CrudRepository<T,?>冲突！！
     * */
    public <T extends SliceSyncRes, RP extends PagingAndSortingRepository<T,?> & CrudRepository<T,?>> JsliceMang  sliceJobBatch(JsliceMang mng,RP pagingAndSortingRepository, Function<SliceSyncRes, Triple> sliceJob,
                                                                                                                                CrudRepository repository1, CrudRepository repository2){
        Pageable pageable= PageOffsetFirst.of(mng.getOffs(),mng.getLimt());
        //分片任务，保证可以重复执行，确保findAll读取出来的记录有顺序。
        log.info("run:作业{},Off={}", mng.getName(), mng.getOffs());
        Iterable<T>  pall= pagingAndSortingRepository.findAll(pageable);
        int count=0;
        List rpl1=new ArrayList<>();
        List rpl2=new ArrayList<>();
        for (T parent:pall) {
            Triple triple=null;
            Boolean execOK=true;
            try {
                //返回需要批量存储仓库的实体1，2：
                triple=sliceJob.apply(parent);
                String result= (String)triple.getVal1();
                //错误提示result，不代表绝对没有生成可用数据，有些仅仅算提示。
                if(StringUtils.hasText(result) )
                    ((SliceSyncRes)parent).setFail(result);
                else
                    ((SliceSyncRes)parent).setFail(null);
                Object enty1= triple.getVal2();
                if(null!=enty1)     rpl1.add(enty1);
                Object enty2= triple.getVal3();
                if(null!=enty2)     rpl2.add(enty2);
            } catch (Exception e) {
                e.printStackTrace();
                execOK= false;
            }
            if(!execOK){
                log.error("报错暂停,该后退一个limit; offs={}重来",mng.getOffs());     //还会出现的 错误种类太多了
                mng.setDesc("报错暂停,该后退一个limit");   //报错暂停应该后退一个limit
                //mng.setOffs(mng.getOffs() + count);
                //取消批量存储？ 一整批抛弃？原先最多弃单个
                return mng;
            }
            count++;
            LocalDateTime now=java.time.LocalDateTime.now();
            java.time.Duration duration = java.time.Duration.between(mng.getLast(), now);
            //怕时间太长，影响任务抢占了。分片运行有效时间太短了反而没机会抢到。
            if(duration.toSeconds()>60)
                break;
        }
        //最后一批一起保存。 ？把ES批量保存和数据库事务拆开，CRDB事务只管数据库的作业，ES保存时间要拖出去。
        if(null!=repository1)   repository1.saveAll(rpl1);   //居然5秒就保存超时失败导致分片任务终止
        if(null!=repository2)   repository2.saveAll(rpl2);
        pagingAndSortingRepository.saveAll(pall);
        mng.setOffs(mng.getOffs() + count);
        return mng;
    }
    /**手动配置分片作业的输入条件： 数据库配合设置, 若分片数据过大导致事务提交失败
     * 再依照运行log日志查看这些分片条件的实际运行成功提交与否。
     * */
    public Boolean setupFromLegacy_Jf(JsliceMang jobm,String arg) {
        Boolean retIner;
        final String[] jB限制事务={"ID<=22000","ID>22000 AND ID<=44000","ID>44000 AND ID<=66000","ID>66000 AND ID<=88000",
                "ID>88000 AND ID<=110000","ID>110000"};  //当前id最大144091
        if("jB".equals(arg)) {
            if(jobm.getOffs() >= jB限制事务.length || jobm.getOffs()<0) {
                jobm.setDesc("无法前进offset");
                return false;
            }
            retIner=stepJob_JU监察部门(jobm.getOffs(), jB限制事务[jobm.getOffs()] );
            if(retIner)  jobm.setOffs(jobm.getOffs()+1);        //默认limit=1++
        }
        else return false;
        return retIner;
    }
    /**设备列表：cod="",oid=QH07421,邵武市的竟然是江西来检验！'中国铁路南昌局集团有限公司特种设备检测检验所'铁路系统特种设备作业人员培训;
     * 太慢1万条18秒才搞定；可用upsertAll替换之后2.23秒生成1万条，速度提高8倍。
     * */
    public Boolean syncEqpFromLegacy_AK(JsliceMang jobm,String arg) {
        log.info("syncEqpFromLegacy_AK: 开始 offset={} *1000",jobm.getOffs());
        int stepSz=1000;       //当前条数877408 ,一分片太多条无法成功提交
        synchronized (this) {      //必须加锁：一个用户连续点击按钮2次： volatile static Singleton
            if(null==buffAKread) {              //直接快照化存到文件，不需要每次调试运行都要从远端json读太吃内存。
                buffAKread= (ReadComm) Tool.readOjbectFromFile("buff_AK_List.dat");     //下次重做必须删除该文件！D:/home/unibackend/
                if(null==buffAKread){
                    //【json Object问题】IDEA:Debug运行可能无法正常太吃内存，需要RUN方式运行。对象在内存当中比在数据库当中的空间占用大得多。
                    buffAKread = (ReadComm) Utilities.getItArr(614999, "");    //Accept not text/html
                    log.info("syncEqpFromLegacy_AK:{},远端读取={}开始写文件buff_AK_List.dat",buffAKread.getErrorCode(), buffAKread.getArr().size());
                    Tool.writeObjectToFile(buffAKread, "buff_AK_List.dat");
                }
                log.info("syncEqpFromLegacy_AK:从文件恢复到内存运行太慢,cod总数={}", buffAKread.getArr().size());
            }
            else {
                if(jobm.getOffs() *stepSz >= buffAKread.getArr().size() || jobm.getOffs()<0) {
                    jobm.setDesc("无法前进offset");
                    return false;
                }
            }
        }
        int countall=buffAKread.getArr().size();
        //终端交互才会用System.out.println((new String()).format("例行Utilities.average运行=%d",result.getErrorCode()) );堆溢出！
        int nowcnt=0;
        int lopct=0;
        List<StoreSync> batch=new ArrayList<>();
        int headis=jobm.getOffs() *stepSz;
        log.info("syncEqpFromLegacy_AK: 序号headis={}",headis);
        for(int i =headis; i<headis+stepSz && i<countall; i++)
        {
            Object one1=buffAKread.getArr().get(i);
            KeyLoop one= (KeyLoop)one1;
            lopct++;
            nowcnt++;
            StoreSync ado=new StoreSync();
            ado.setCod(one.getEQP_COD());
            ado.setOid(one.getOIDNO());
            ado.setId(UUID.randomUUID());    //upsert、upsertAll并且id还没自动生成的情况才需要；
            batch.add(ado);
            if(lopct>300) {
                storeSyncRepository.upsertAll(batch);
                lopct=0;
                batch.clear();
            }
        }
        storeSyncRepository.upsertAll(batch);
        log.info("syncEqpFromLegacy_AK:完成！offs={},条数{}接着等数据库后台提交",jobm.getOffs(), countall);  //一直在写.log?比mySQL慢多了30分钟才能让磁盘安静
        jobm.setOffs(jobm.getOffs()+1);
        //【奇异】超时GraphQL取消过后，等了一段时间过后事务会bTransactionAbortedError(ABORT_REASON_CLIENT_REJECT)，接着还会重做这个作业事务函数：都已经没人管孤儿了。
        return true;         //事务DB还在提交进行中，也会应答前端：GraphQL execution canceled because timeout of 900000 millis was reached
    }
    /**分片作业 更新ES等 , 根据EqpES生成下年检验任务。
     * 下面"bC" 或 eqpEsRepository 和 makeFor单个ES维护bC ： @都已经淘汰，可删除！！
     * */
    @Transactional(readOnly=true)
    public Boolean makeSomeForSI(JsliceMang jobm,String arg) {
        LocalDate today= LocalDate.now();
        Function<?, Triple<?,?,?>>  sliceJob= parb ->{
            //【泛型】问题：运行期才会报错class java.util.ArrayList cannot be cast to class org.fjsei.yewu.index.EqpEs；旧的makeFor单个SI维护bC((EqpEs) parb);
            if("bC".equals(arg))    return makeFor单个SI维护bC((List<EqpEs>) parb);
            else if("Ts".equals(arg))    return makeFor单个SI任务生成((List<EqpEs>) parb, 0);
            else if("ND".equals(arg))    return makeFor单个SI任务生成((List<EqpEs>) parb, 1);
            else return null;
        };
        //注入：合并打包的规则, 为每个包运行sliceJob()一次。
        BiFunction<EqpEs, EqpEs, Boolean>  packFunc= (base, cmpob)->{
            if("Ts".equals(arg)) {
                return (base.getUseu().equals(cmpob.getUseu()) && base.getNxtd2().equals(cmpob.getNxtd2()) );
            }
            else if("ND".equals(arg)){
                return ( base.getNxtd1().equals(cmpob.getNxtd1()) && base.getUseu().equals(cmpob.getUseu()) );
            }
            return false;       //不合并的
        };
        //因为spring-data-elasticsearch-5版本SearchAfter不支持Date的类型;统一搞都FieldValue.of(Kind.String)
        BiFunction<Object, Boolean, Object>  searchAfterCvtFn= (from, str2List)->{
            if("Ts".equals(arg) || "ND".equals(arg)) {
                if(str2List){
                    return JSON.parseObject((String) from, List.class);
                }else {
                    List<Object> afterObj= (List<Object>) from;
                    List<Object> newList=new ArrayList<>(afterObj);
                    newList.set(1, longstr2Date((String) afterObj.get(1)));
                    return JSON.toJSONString(newList);
                }
            }
            else if("bC".equals(arg)){
                if(str2List){
                    return JSON.parseObject((String) from, List.class);
                }else {
                    List<Object> afterObj= (List<Object>) from;
                    List<Object> newList=new ArrayList<>(afterObj);
                    newList.set(1, longstr2Date((String) afterObj.get(1)));
                    newList.set(2, longstr2Date((String) afterObj.get(2)));
                    return JSON.toJSONString(newList);
                }
            }
            return null;
        };
        Pageable pageable;
        if("bC".equals(arg)){       //仅仅测试目的：ES8从API获取的是string long表达的日期，要设置的输入的却要求是“uuuu-MM-dd”，中间经过spring-data-elasticsearch搞得鬼。
            List<Query> listquerys = new ArrayList<Query>();
            //【报错】因为用了"_id"排序聚合 “Fielddata access on the _id field is disallowed, you can re-enable it by updating the dynamic cluster setting: indices.id_field_data.enabled”
            //_id字段的值可以用来进行聚合和排序，但不鼓励这样做，因为它需要在内存中加载大量数据。如果需要对_id字段进行排序或聚合，建议使用另外一个字段来存储与_id一样的值;
            //_id字段被禁止在聚合、排序和脚本中使用。如果需要对_id字段进行排序或聚合，建议将_id字段的内容复制到doc_values的字段中。
            //ES8排序字段"_id"被禁用会报错了，只好多两个（cod + ispu.id）来接替了，确保分页的排序和全量数据检索严谨性; 也不能用"_uid"；
            //Sort sort=Sort.by(Sort.Order.asc("useu.id"),Sort.Order.asc("nxtd2"),Sort.Order.asc("nxtd1"),Sort.Order.asc("_id"));
            Sort sort=Sort.by(Sort.Order.asc("useu.id"),Sort.Order.asc("nxtd2"),Sort.Order.asc("nxtd1"),Sort.Order.asc("cod_sort"),Sort.Order.asc("ispu.id"));
            pageable = PageOffsetFirst.of(0, jobm.getLimt(), sort);
//            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder().withQuery(
//                    boolQuery().must(
//                            matchPhraseQuery("sort", "31").slop(7)
//                    )
//            ).withPageable(pageable);
//            NativeSearchQueryBuilder qb = new NativeSearchQueryBuilder().withPageable(pageable);
            //本身 SELF<>  BaseQueryBuilder<Q, SELF> builder=; 所以可以直接变成的
            NativeQueryBuilder  nativeQueryBuilder = NativeQuery.builder()
                    .withFilter(q -> q.bool(
                                    v->v.must(listquerys)
                            )
                    );
            NativeQuery query =nativeQueryBuilder.withPageable(pageable).build();
            return searchAfterEsP(jobm, query, sliceJob, null, null,EqpEs.class,packFunc,searchAfterCvtFn);
        }
        else if("Ts".equals(arg)){
            List<Query> listquerys = new ArrayList<Query>();
            Sort sort=Sort.by(Sort.Order.asc("useu.id"),Sort.Order.asc("nxtd2"),Sort.Order.asc("cod_sort"),Sort.Order.asc("ispu.id"));
            pageable = PageOffsetFirst.of(0, jobm.getLimt(), sort);
            //前置限制 6个基本过滤 nxtd2 >='2022-08-17' and  nxtd2<='2023-08-17' 数据量大大减少
            //【严重】下检日都过了还没任务启动的不应该放在这里去生成任务： 超期未检的应该从监察平台监控；本平台前面步骤可预处理这种；【假设】我这环节没有超期未检设备。
//            旧版本 NativeSearchQueryBuilder qb = new NativeSearchQueryBuilder().withQuery(
//                    boolQuery().must( rangeQuery("nxtd2").gte(today).lte(today.plusYears(1)) )
//                            .must(new ExistsQueryBuilder("useu.id")).must(new ExistsQueryBuilder("svu.id")).must(new ExistsQueryBuilder("ispu.id"))
//                            .mustNot(new TermsQueryBuilder("reg", "CANCEL")).must(new TermsQueryBuilder("ust", "USE","USENOTREG"))
//            ).withPageable(pageable);
            listquerys.add(Query.of(q -> q
                    .range(m -> m
                            .field("nxtd2")
                            .from(String.valueOf(today)).to(String.valueOf(today.plusYears(1)))
                    )
            ));
            listquerys.add(Query.of(c -> c
                    .exists(m -> m.field("useu.id")) )
            );
            listquerys.add(Query.of(c -> c
                    .exists(m -> m.field("svu.id")) )
            );
            listquerys.add(Query.of(c -> c
                    .exists(m -> m.field("ispu.id")) )
            );
            listquerys.add(Query.of(c -> c
                    .terms(m -> m
                            .field("ust").terms(k->k
                                    .value(List.of(FieldValue.of("USE"), FieldValue.of("USENOTREG")))
                            )
                    ) )
            );
            //不应嵌入这里？ .mustNot(new TermsQueryBuilder("reg", "CANCEL"))  ;可惜Query.of(c -> c.真没法直接套接must mustNot should()的！！必须加一层的.bool();bool做顶层或聚合逻辑层。逻辑嵌套模式;
            //【注意】两个同名字方法的实际来自不同包！前面是 co.elastic.clients.elasticsearch._types.query_dsl.Query.Builder.terms()后面是 co.elastic.clients.elasticsearch._types.query_dsl.TermsQuery.Builder.terms()
            listquerys.add(Query.of(c -> c
                            .bool(s -> s
                                    .mustNot(q->q
                                            .terms(m -> m
                                                    .field("reg").terms( k->k
                                                            .value(List.of(FieldValue.of("CANCEL")) )
                                                    )
                                            )
                                    )
                            )
                    )
            );
            NativeQueryBuilder  nativeQueryBuilder = NativeQuery.builder()
                    .withFilter(q -> q
                            .bool(v->v
                                    .must(listquerys)
                            )
                    );
            NativeQuery query =nativeQueryBuilder.withPageable(pageable).build();
            return searchAfterEsP(jobm, query, sliceJob, null, null,EqpEs.class,packFunc,searchAfterCvtFn);
        }
        else if("ND".equals(arg)){
            List<Query> listquerys = new ArrayList<Query>();
            Sort sort=Sort.by(Sort.Order.asc("useu.id"),Sort.Order.asc("nxtd1"),Sort.Order.asc("cod_sort"),Sort.Order.asc("ispu.id"));
            pageable = PageOffsetFirst.of(0, jobm.getLimt(), sort);
//            NativeSearchQueryBuilder qb = new NativeSearchQueryBuilder().withQuery(
//                    boolQuery().must( rangeQuery("nxtd1").gte(today).lte(today.plusYears(1)) )
//                            .must(new ExistsQueryBuilder("useu.id")).must(new ExistsQueryBuilder("svu.id")).must(new ExistsQueryBuilder("ispu.id"))
//                            .mustNot(new TermsQueryBuilder("reg", "CANCEL")).must(new TermsQueryBuilder("ust", "USE","USENOTREG"))
//            ).withPageable(pageable);
            listquerys.add(Query.of(q -> q
                    .range(m -> m
                            .field("nxtd1").from(String.valueOf(today)).to(String.valueOf(today.plusYears(1)))
                    )
            ));
            listquerys.add(Query.of(c -> c
                    .exists(m -> m.field("useu.id")) )
            );
            listquerys.add(Query.of(c -> c
                    .exists(m -> m.field("svu.id")) )
            );
            listquerys.add(Query.of(c -> c
                    .exists(m -> m.field("ispu.id")) )
            );
            listquerys.add(Query.of(c -> c
                    .terms(m -> m
                            .field("ust").terms(k->k
                                    .value(List.of(FieldValue.of("USE"), FieldValue.of("USENOTREG")))
                            )
                    ) )
            );
            //非多项的单个匹配可简化 .term(m -> m.field("reg").value("CANCEL")
            listquerys.add(Query.of(c -> c
                            .bool(s -> s
                                    .mustNot(q->q
                                            .terms(m -> m
                                                    .field("reg").terms( k->k
                                                            .value(List.of(FieldValue.of("CANCEL")) )
                                                    )
                                            )
                                    )
                            )
                    )
            );
            NativeQueryBuilder  nativeQueryBuilder = NativeQuery.builder()
                    .withFilter(q -> q
                            .bool(v->v
                                    .must(listquerys)
                            )
                    );
            NativeQuery query =nativeQueryBuilder.withPageable(pageable).build();
            return searchAfterEsP(jobm, query, sliceJob, null, null,EqpEs.class,packFunc,searchAfterCvtFn);
        }
        else return false;   //泛型决定parb最终类型。 sliceJob是嵌入在Loop内部的函数块。
    }
    public Boolean setupFromLegacy_Ls临时Isp消除(String arg) {
        Pageable pageable= PageRequest.of(0, 999);
        QIsp q=QIsp.isp;        //Isp表设置dev_id可实际Eqp不存在的? q.dev.id.isNull()也不行啊:不正常数据，关联无效，逻辑？
        Predicate predicate= q.dev.cod.isNull().or(q.dev.oid.isNull());      //.or.导致只能是全表搜索的
        QBeanMy<?> piExp=new QBeanMy<Isp>(Isp.class, q.id );
        piExp.bindLeftJoin(q.dev);      //默认Cross join; 下面这条执行很慢！ 外键索引还没有建立？
        Slice<IspPi> slice= iSPRepository.findBy(piExp, predicate, (query)-> {
            FluentQuery.FetchableFluentQuery<IspPi> queryUse = query.as(IspPi.class);
            return  queryUse.page(pageable);
        });
        List<IspPi> result=slice.getContent();
        log.info("准备清理Isp错误Dev关联 条数{}", result.size());
        List<UUID> ids= result.stream().map(isppi->isppi.getId()).collect(Collectors.toList());
        iSPRepository.deleteAllByIdInBatch(ids);     //自己isp.ID删除也很慢? 删442条 em.flush()?
        return true;
    }
    //分片作业，Detail检验任务 更新等；
    //这底下的Function<DetailPi, Triple<?,?,?>>一定要标注类型,否则编译错。
    public Boolean makeSomeForDT(JsliceMang jobm,String arg) {
        Function<Detail, Triple<?,?,?>>  sliceJob= parb ->{
            if("Oo".equals(arg))    return makeFor单DT检验任务Oo(parb);
            else return null;
        };
        if("Oo".equals(arg)){
            return searchAfter(jobm, details, sliceJob, null, null, Detail.class);
        }
        else return false;   //泛型决定parb最终类型。 sliceJob是嵌入在Loop内部的函数块。
    }
    /*每个Detail任务; 提前准备索引
    改关联关系【流程】首先附加临时字段tisp,tbus;数据转赋值=前端维护12.01批处理;除旧关联字段isp.bus_id{索引外键}临时字段改正常名detail.tisp_id=>isp_id{索引外键};源程序修改代码和数据库同步git。
    * */
    public <T> Triple  makeFor单DT检验任务Oo(T inObj) {
        //投影类无法转 class com.sun.proxy.$Proxy410 cannot be cast to class md.specialEqp.inspect.Detail
        Detail detail= (Detail) inObj;
//        Isp isp=detail.getIsp();
//        if(null!=isp) {       //临时设置功能已完成使命，#删除掉!
//            isp.setTbus(detail);
//        }
        return new Triple("", null, null);
    }
    /**关系数据库同步到ES搜索索引
     * 返回参数不一样，把作业结果直接返回。
     * */
    public String setupFromLegacy_SY(String arg) {
        if("Ov".equals(arg))    return setupFromLegacy_SY结束否(arg);
        else if("ca".equals(arg))    return setupFromLegacy_SY取消(arg);
        else if("Is".equals(arg))    return setupFromLegacy_SY启动("Isp");
        else if("cP".equals(arg))    return setupFromLegacy_SY启动("Company");
        else if("pS".equals(arg))    return setupFromLegacy_SY启动("Person");
        else if("eQ".equals(arg))    return setupFromLegacy_SY启动("Eqp");
        else if("Pu".equals(arg))    return setupFromLegacy_SY启动("PipingUnit");
        else return null;
    }
    public String setupFromLegacy_SY结束否(String arg) {
        Boolean ok = searchService.isCompleteMassIndex(null);
        Duration duration = Duration.between(searchService.getBeginTime(), Instant.now());
        MyMassIndexingLoggingMonitor monitor=searchService.getMassIndexingMonitor();
        Assert.notNull(monitor, "沒开启");
        if(ok)   duration= Duration.between(searchService.getBeginTime(), monitor.getCompleteTime());
        String desc="同步索引"+(ok? "任务结束,用时" : "进行中,持续") +duration.toString()+",比例"+monitor.getFinishPercent()*100
                            + "实体:" + searchService.getMassindexEntities();
        log.info(desc);
        return desc;
    }
    public String setupFromLegacy_SY取消(String arg) {
        Boolean ok = searchService.cancelMassIndex(null);
        Duration duration = Duration.between(searchService.getBeginTime(), Instant.now());
        MyMassIndexingLoggingMonitor monitor=searchService.getMassIndexingMonitor();
        Assert.notNull(monitor, "沒开启");
        String desc="取消同步"+(ok? "成功" : "失败")+",持续"+duration.toString()+",比例"+monitor.getFinishPercent()*100
                            + "实体:" + searchService.getMassindexEntities();
        log.info(desc);
        return desc;
    }
    public String setupFromLegacy_SY启动(String arg) {
        Boolean ok = searchService.startMassIndex(arg);
        String desc="启动同步ES索引"+(ok? "成功" : "失败")+",实体:"+arg;
        log.info(desc);
        return desc;
    }
}
